背景
在并发编程学习中,认识到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引入了锁的升级机制。锁的状态会根据竞争情况从无锁状态逐步升级到重量级锁:
- 无锁状态:对象未被锁定,Mark Word存储哈希码等信息。
- 偏向锁:如果只有一个线程访问对象,JVM会将锁偏向该线程,减少锁的竞争。
- 轻量级锁:如果有多个线程偶尔竞争同一对象的锁,JVM会将锁升级为轻量级锁,使用CAS操作来减少线程阻塞。
- 重量级锁:如果多个线程频繁竞争同一对象的锁,JVM会将锁升级为重量级锁,导致线程阻塞,性能较低。
5. 总结
| 锁类型 | 定义 | 使用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 偏向锁 | 适用于单线程或低竞争场景,将锁偏向某个特定线程 | 单线程访问、低竞争场景 | 减少锁的开销,提高性能 | 不适合高竞争场景,撤销操作带来额外开销 |
| 轻量级锁 | 基于CAS操作的锁机制,适用于偶发性竞争场景 | 偶发性竞争、短时间同步 | 减少线程阻塞,适用于短时间同步 | 不适合长时间同步,自旋操作浪费CPU资源 |
| 重量级锁 | 传统的锁机制,基于操作系统级别的线程调度和阻塞 | 高竞争场景、长时间同步 | 确保线程的安全性,适用于高竞争场景 | 性能较低,导致线程上下文切换 |
6. 选择合适的锁机制
- 单线程或低竞争场景:使用偏向锁可以显著提高性能,减少锁的开销。
- 偶发性竞争场景:使用轻量级锁可以减少线程阻塞,适用于短时间同步。
- 高竞争场景:使用重量级锁可以确保线程的安全性,虽然性能较低,但在高竞争场景下是必要的。
通过理解这些锁机制的区别和适用场景,你可以更好地选择合适的锁策略,编写更高效、更安全的多线程代码。