Java 中的 synchronized
是一种常用的线程同步机制,它通过内置的锁(监视器锁,Monitor Lock)来保护代码块或方法的并发安全。从 JDK 1.6 开始,synchronized
进行了许多优化,其中一个重要的机制是自适应锁(Adaptive Spinning)。
1. 什么是自适应锁?
自适应锁是一种优化锁竞争和线程上下文切换性能的技术。传统情况下,线程在竞争锁失败时,会直接进入操作系统的阻塞状态。然而,这种阻塞和唤醒涉及系统调用,开销较大。自适应锁通过自旋锁的改进来减少这种开销。
- 自旋锁:线程在竞争锁失败时,不立即进入阻塞状态,而是执行一段空循环(自旋)。如果锁在短时间内被释放,线程可以立即获得锁。
- 自适应自旋锁:改进的自旋锁,自旋时间是动态调整的,基于:
- 锁的状态(是否频繁被持有或释放)。
- 线程的历史表现(上一次自旋是否成功)。
如果一个线程在之前自旋中成功获得了锁,JVM 会认为锁的竞争较少,在下一次尝试时可能增加自旋时间;反之,如果多次自旋失败,JVM 会减少自旋或直接放弃自旋,进入阻塞。
2. 自适应锁的实现原理
自适应锁的过程
- 初始状态:
- 当线程尝试获取锁时,检查锁是否被持有。
- 如果锁空闲,直接获得锁。
- 如果锁被持有,进入自适应自旋阶段。
- 自旋逻辑:
- 基于锁的状态和线程历史决定自旋的次数:
- 锁被短时间持有:线程自旋一段时间等待锁释放。
- 锁竞争激烈:线程直接放弃自旋,进入阻塞。
- 基于锁的状态和线程历史决定自旋的次数:
- 锁释放:
- 持有锁的线程释放锁后,自旋线程可能立即获得锁。
- 如果没有等待的线程,锁进入空闲状态。
自适应自旋的条件
- JVM 会根据 CPU 核心数启用自适应自旋机制(多核 CPU 上效果更好)。
- 自适应自旋的时间由 JVM 动态调整,无需开发者干预。
- 自适应锁是一种轻量级锁,不涉及内核调用(减少上下文切换)。
3. synchronized
的锁优化演进
Synchronized
的优化涉及多种锁状态的转换和自适应策略,主要有以下几种锁状态:
- 无锁(No Lock):
- 当代码没有竞争时,直接执行,无需加锁。
- 偏向锁(Biased Locking):
- 锁偏向第一个获取它的线程,减少 CAS 操作的开销。
- 偏向锁在没有其他线程竞争时非常高效。
- 轻量级锁(Lightweight Lock):
- 当多个线程尝试竞争锁时,使用 CAS 操作加锁。
- 如果竞争不激烈,轻量级锁性能优于重量级锁。
- 重量级锁(Heavyweight Lock):
- 多线程竞争严重时,升级为重量级锁,线程进入阻塞状态。
- 依赖操作系统的 Mutex 机制,开销较高。
4. 自适应锁的优点
- 减少上下文切换: 自旋避免了线程频繁进入阻塞和唤醒状态,特别适合锁持有时间短的场景。
- 动态调整: 自适应锁通过动态调整自旋时间,平衡了自旋时间和阻塞等待之间的性能。
- 适用性强: 自适应锁特别适合 CPU 密集型任务,能够充分利用 CPU。
5. 自适应锁的局限性
- 锁持有时间长: 如果锁长时间被持有,自旋会浪费 CPU 资源。
- 线程竞争激烈: 多线程竞争时,频繁自旋可能导致 CPU 占用过高。
- 依赖 JVM 实现: 自适应锁是 JVM 的实现细节,开发者无法直接干预。
6. 自适应锁的调优
尽管 synchronized
的自适应锁机制已非常高效,但在实际开发中,可以通过以下方式进一步优化:
- 减少锁的粒度:
- 将锁的范围尽量缩小,避免大范围的同步代码块。
- 使用更高效的锁机制:
- 对于高并发场景,可以考虑使用
ReentrantLock
或其他java.util.concurrent
包中的锁。
- 对于高并发场景,可以考虑使用
- 避免长时间持有锁:
- 尽量减少锁中包含的耗时操作,如 I/O 或网络调用。
7. 示例代码分析
以下代码展示了锁的不同竞争情况:
public class SynchronizedTest {
private static int count = 0;
public synchronized static void increment() {
count++;
}
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + count);
}
}
运行时观察:
- 如果线程竞争不激烈(单线程或短时间内竞争),JVM 会利用偏向锁或轻量级锁提升性能。
- 如果线程竞争激烈,JVM 会升级为重量级锁,并可能利用自适应自旋优化性能。
总结
自适应锁是 Java 并发性能优化的重要一环,通过动态调整自旋时间,减少线程上下文切换的开销。虽然开发者无法直接控制自适应锁的行为,但可以通过优化锁的使用方式和代码结构,间接提升性能。
发布者:myrgd,转载请注明出处:https://www.object-c.cn/4352