在java中,synchronized锁的膨胀是指锁从轻量级状态逐步升级为更重的状态,以适应并发情况的变化。这种锁升级机制是为了在不同的线程竞争强度下提供合适的锁实现,最大化性能。锁的膨胀过程从“无锁”到“偏向锁”,再到“轻量级锁”,最终可能升级为“重量级锁”。这个机制通过减少不必要的线程阻塞和上下文切换,提高了性能。
锁的几种状态
1. 无锁状态(No Lock)
适用场景:线程不涉及任何锁竞争时,代码执行不会有锁的开销。
特点:不需要加锁,适用于没有并发的场景。
2. 偏向锁(Biased Locking)
原理:偏向锁是一种优化技术,锁对象会偏向于第一个获取它的线程。锁的偏向意味着同一个线程可以反复进入锁,而无需执行锁的操作。
适用场景:同一线程多次进入同步块的情况(如无竞争或竞争少的情况下)。
特点:
- 偏向锁允许一个线程在获取锁后,直接进入临界区而不再进行CAS(Compare-And-Swap)操作,减少了锁获取的开销。
- 偏向锁只在没有其他线程尝试竞争该锁时生效。
偏向锁撤销:如果有其他线程尝试竞争该锁,偏向锁会被撤销,升级为轻量级锁。
3. 轻量级锁(Lightweight Locking)
原理:轻量级锁是在偏向锁升级后的一种锁状态。它通过使用CAS操作来避免线程阻塞,减少性能开销。轻量级锁的核心思想是:如果锁是空闲的,线程可以通过CAS操作成功获取锁;如果锁已被其他线程持有,线程会通过自旋等待,而不是立即进入阻塞状态。
适用场景:多线程并发时有竞争但不激烈的场景。
特点:
- 当锁处于轻量级状态时,如果另一个线程尝试获取该锁,当前线程会自旋等待锁的释放,而不立即进入阻塞。
- 如果自旋时间过长,或者锁竞争过于激烈,轻量级锁会膨胀为重量级锁。
- 轻量级锁的好处是尽量避免线程切换和阻塞。
4. 重量级锁(Heavyweight Locking)
原理:重量级锁是锁的最高级别形态,它通过操作系统的监视器锁(OS的Mutex)来实现,线程在竞争锁时,如果无法获取锁,会直接进入阻塞状态,等待操作系统调度。
适用场景:多线程竞争非常激烈的情况下。
特点:
- 线程在争抢锁时,直接被阻塞,避免了CPU的过多消耗。
- 但是,线程在进入和退出阻塞状态时需要进行上下文切换,开销较大。
- 重量级锁会导致JVM与操作系统进行频繁的线程调度操作,降低系统性能。
锁膨胀过程
锁的膨胀是随着线程竞争的激烈程度逐步升级的过程:
1. 初始状态(无锁状态):
当一个对象刚创建时,锁并没有被加上,处于“无锁”状态。此时没有任何线程访问共享资源,程序运行在无锁的状态下。
2. 偏向锁:
• 当第一个线程访问同步块时,JVM会为这个线程设置“偏向锁”。此时,锁不会涉及复杂的CAS操作,只需检查对象头中是否已经设置了偏向锁的标记。
• 偏向锁的优点在于它适用于没有锁竞争的场景,极大地减少了锁获取的开销。
3. 偏向锁撤销:
如果有其他线程竞争该锁,偏向锁会撤销,锁会升级为轻量级锁。这个过程涉及到锁的膨胀——撤销偏向锁可能会暂停正在执行的线程,进入一个安全点,进行偏向锁的升级。
4. 轻量级锁:
• 轻量级锁通过CAS操作尝试获取锁,如果成功,线程可以进入临界区。如果CAS失败,意味着锁已被其他线程持有,当前线程会进行自旋等待,而不会立刻进入阻塞。
• 轻量级锁的优势在于避免了线程阻塞的高开销,但如果锁竞争激烈,自旋等待会浪费大量的CPU资源。
5. 轻量级锁膨胀为重量级锁:
如果有多个线程在争抢锁,且自旋等待的时间过长或锁竞争过于激烈,轻量级锁会膨胀为重量级锁。这时线程会进入阻塞状态,等待锁的释放。重量级锁通过操作系统的同步机制来控制线程的调度。
锁降级
当 JVM 进入安全点的时候,会检查是否有闲置的锁,然后进行降级。降级对象为仅仅能被 VMThread 访问而没有其他 JavaThread 访问的对象。
JVM安全点:
在多线程程序中,当JVM要执行某些全局操作时,如垃圾回收,需要确保所有线程的状态是可知且可控的。在任意时间暂停线程执行,可能会导致数据不一致或线程状态不确定。因此,JVM在执行某些关键操作之前,必须等待所有线程到达某些特定的“安全”位置,这些位置就是安全点。
锁膨胀的性能影响
• 偏向锁和轻量级锁:
这两种锁状态的出现是为了减少传统重量级锁带来的上下文切换和线程阻塞,提高并发性能。在没有竞争或竞争较少的情况下,偏向锁和轻量级锁可以极大地提高并发性能。
• 重量级锁:
一旦膨胀为重量级锁,线程会直接进入阻塞状态,锁的开销变得非常大。大量的上下文切换会带来系统性能的下降。
锁膨胀优化
JVM提供了一些参数,可以通过调整这些参数来优化锁膨胀行为:
• 禁用偏向锁:可以通过-XX:-UseBiasedLocking参数禁用偏向锁,如果系统中存在大量的锁竞争,禁用偏向锁有助于避免偏向锁的频繁撤销。
• 自旋次数设置:可以通过参数-XX:PreBlockSpin来设置自旋锁的自旋次数。如果自旋次数过少,会导致锁快速膨胀为重量级锁;如果自旋次数过多,则可能浪费CPU资源。
总结
synchronized锁的膨胀是为了在不同的并发环境下实现最佳性能。Java虚拟机(JVM)通过偏向锁、轻量级锁和重量级锁的分级机制来优化锁的性能:
• 偏向锁:适用于无竞争场景,消耗最小。
• 轻量级锁:适用于有一定竞争但不激烈的场景,通过自旋避免线程阻塞。
• 重量级锁:适用于高并发场景,通过线程阻塞和上下文切换来处理锁竞争。
1111

被折叠的 条评论
为什么被折叠?



