java并发编程中,偏向锁-轻量级锁-重量级锁,定义,区别,适用场景,你清楚吗?

228 阅读8分钟

背景

在并发编程学习中,认识到java中锁 有 偏向锁 轻量级锁 重量级锁,于是好奇他们之间是有什么联系,什么时候开始切换下一状态,怎么切换下一状态,于是进行搜索,现将搜索结果整理如下:

偏向锁-轻量级锁-重量级锁,定义,区别,适用场景

在Java中,锁的实现经历了从重量级锁到轻量级锁再到偏向锁的优化过程。这些锁机制旨在提高多线程环境下的性能,减少线程竞争和上下文切换的开销。以下是偏向锁轻量级锁重量级锁的定义、区别以及使用场景的详细解释。

1. 偏向锁 (Biased Locking)

定义:

  • 偏向锁是一种优化的锁机制,适用于只有一个线程访问同步块的场景。它允许线程在第一次获取锁时,将对象头的Mark Word标记为偏向该线程。之后,该线程再次访问该对象时,无需进行额外的锁操作,直接进入同步块。
  • 偏向锁的核心思想是:如果一个对象在多线程环境中总是由同一个线程访问,那么可以将锁偏向该线程,避免频繁的加锁和解锁操作,从而提高性能。

工作原理:

  • 初始状态:对象处于无锁状态,Mark Word存储哈希码等信息。
  • 偏向锁获取:当第一个线程尝试获取锁时,JVM会检查对象是否已被其他线程偏向。如果没有,则将锁偏向当前线程,并在Mark Word中记录线程ID。
  • 偏向锁持有:如果后续的同步操作仍然是由同一个线程发起的,JVM只需检查Mark Word中的线程ID是否匹配,匹配则直接进入同步块,无需进行CAS操作或阻塞。
  • 偏向锁撤销:如果有其他线程尝试获取同一对象的锁,JVM会撤销偏向锁,升级为轻量级锁或重量级锁。撤销操作涉及恢复Mark Word的原始状态,并通知所有等待的线程。

使用场景:

  • 单线程访问:当一个对象几乎总是由同一个线程访问时,偏向锁可以显著提高性能。例如,某些缓存对象或局部变量可能只被一个线程使用。
  • 低竞争场景:在竞争不激烈的情况下,偏向锁可以减少锁的开销,避免频繁的锁争用。

优点:

  • 减少锁的开销:偏向锁避免了频繁的加锁和解锁操作,减少了CAS操作和上下文切换的开销。
  • 提高性能:在单线程或低竞争场景下,偏向锁可以显著提升性能。

缺点:

  • 不适合高竞争场景:如果多个线程频繁竞争同一对象的锁,偏向锁的撤销操作可能会带来额外的开销。
  • JVM默认启用:偏向锁是JVM的默认行为,但可以通过-XX:-UseBiasedLocking禁用。

2. 轻量级锁 (Lightweight Locking)

定义:

  • 轻量级锁是一种基于CAS(Compare-And-Swap)操作的锁机制,适用于多个线程偶尔竞争同一对象的场景。它通过在对象头的Mark Word中存储指向栈中锁记录的指针来实现锁的状态管理。
  • 轻量级锁的核心思想是:在没有竞争的情况下,使用CAS操作来快速获取锁,避免线程阻塞;在有竞争的情况下,升级为重量级锁。

工作原理:

  • 初始状态:对象处于无锁状态,Mark Word存储哈希码等信息。
  • 轻量级锁获取:当第一个线程尝试获取锁时,JVM会通过CAS操作将Mark Word更新为指向栈中锁记录的指针。如果CAS成功,线程获得锁并继续执行。
  • 轻量级锁持有:如果后续的同步操作仍然是由同一个线程发起的,JVM只需检查Mark Word中的指针是否指向当前线程的锁记录,匹配则直接进入同步块。
  • 轻量级锁竞争:如果有其他线程尝试获取同一对象的锁,JVM会检测到锁已经被占用,此时会尝试自旋(Spin Lock),即在一个短时间内的循环中不断尝试获取锁。如果自旋成功,线程获得锁;如果自旋失败,锁会升级为重量级锁,线程被阻塞。

使用场景:

  • 偶发性竞争:当多个线程偶尔竞争同一对象的锁时,轻量级锁可以减少线程阻塞的开销,提高性能。例如,某些共享资源的访问频率较低,或者多个线程交替访问不同对象的锁。
  • 短时间同步:轻量级锁适用于同步块较短的场景,因为自旋操作的时间有限,适合快速获取锁的情况。

优点:

  • 减少线程阻塞:轻量级锁通过自旋操作减少了线程阻塞的可能性,适用于偶发性竞争的场景。
  • 提高性能:在没有竞争的情况下,轻量级锁的性能优于重量级锁,因为它避免了线程的上下文切换。

缺点:

  • 不适合长时间同步:如果同步块较长,自旋操作可能会浪费CPU资源,导致性能下降。
  • 不适合高竞争场景:如果多个线程频繁竞争同一对象的锁,轻量级锁会频繁升级为重量级锁,增加开销。

3. 重量级锁 (Heavyweight Locking)

定义:

  • 重量级锁是传统的锁机制,基于操作系统级别的线程调度和阻塞。它通过Monitor对象来管理锁的状态,包含一个等待队列,用于管理等待获取锁的线程。重量级锁的核心思想是:当多个线程竞争同一对象的锁时,未能获取锁的线程会被阻塞,直到持有锁的线程释放锁。
  • 重量级锁适用于高竞争场景,但它会导致线程上下文切换,性能较低。

工作原理:

  • 初始状态:对象处于无锁状态,Mark Word存储哈希码等信息。
  • 重量级锁获取:当第一个线程尝试获取锁时,JVM会将Mark Word更新为指向Monitor对象的指针。Monitor对象包含一个等待队列,用于管理等待获取锁的线程。
  • 重量级锁持有:持有锁的线程可以继续执行同步块,而其他线程会被阻塞,进入等待队列。
  • 重量级锁释放:当持有锁的线程退出同步块时,它会释放锁,并唤醒等待队列中的一个线程。被唤醒的线程重新尝试获取锁,成功后继续执行。

使用场景:

  • 高竞争场景:当多个线程频繁竞争同一对象的锁时,重量级锁可以确保线程的安全性,虽然性能较低,但它保证了线程不会出现竞态条件。
  • 长时间同步:重量级锁适用于同步块较长的场景,因为它可以有效地管理线程的阻塞和唤醒。

优点:

  • 安全性高:重量级锁确保了线程的安全性,防止多个线程同时访问共享资源。
  • 适用于高竞争场景:在多个线程频繁竞争同一对象的锁时,重量级锁可以有效管理线程的阻塞和唤醒。

缺点:

  • 性能较低:重量级锁会导致线程上下文切换,增加了系统的开销,尤其是在高竞争场景下,性能较差。
  • 不适合短时间同步:对于同步块较短的场景,重量级锁的开销较大,不如轻量级锁或偏向锁高效。

4. 锁的升级机制

为了优化锁的性能,JVM引入了锁的升级机制。锁的状态会根据竞争情况从无锁状态逐步升级到重量级锁:

  1. 无锁状态:对象未被锁定,Mark Word存储哈希码等信息。
  2. 偏向锁:如果只有一个线程访问对象,JVM会将锁偏向该线程,减少锁的竞争。
  3. 轻量级锁:如果有多个线程偶尔竞争同一对象的锁,JVM会将锁升级为轻量级锁,使用CAS操作来减少线程阻塞。
  4. 重量级锁:如果多个线程频繁竞争同一对象的锁,JVM会将锁升级为重量级锁,导致线程阻塞,性能较低。

5. 总结

锁类型定义使用场景优点缺点
偏向锁适用于单线程或低竞争场景,将锁偏向某个特定线程单线程访问、低竞争场景减少锁的开销,提高性能不适合高竞争场景,撤销操作带来额外开销
轻量级锁基于CAS操作的锁机制,适用于偶发性竞争场景偶发性竞争、短时间同步减少线程阻塞,适用于短时间同步不适合长时间同步,自旋操作浪费CPU资源
重量级锁传统的锁机制,基于操作系统级别的线程调度和阻塞高竞争场景、长时间同步确保线程的安全性,适用于高竞争场景性能较低,导致线程上下文切换

6. 选择合适的锁机制

  • 单线程或低竞争场景:使用偏向锁可以显著提高性能,减少锁的开销。
  • 偶发性竞争场景:使用轻量级锁可以减少线程阻塞,适用于短时间同步。
  • 高竞争场景:使用重量级锁可以确保线程的安全性,虽然性能较低,但在高竞争场景下是必要的。

通过理解这些锁机制的区别和适用场景,你可以更好地选择合适的锁策略,编写更高效、更安全的多线程代码。