重量级锁是Java中用于实现线程同步的传统锁机制,基于操作系统的互斥量(Mutex)实现。相比轻量级锁和偏向锁,重量级锁在竞争激烈的场景下提供更强的线程安全性,但性能开销较大。
重量级锁的核心机制
依赖操作系统互斥量
重量级锁通过操作系统的内核级互斥量(如Linux的pthread_mutex_t)实现线程同步。当多个线程竞争同一锁时,未获得锁的线程会被挂起(阻塞),进入操作系统内核态等待,直到锁被释放后重新唤醒(用户态与内核态切换)。对象头与Monitor
- 重量级锁状态下,对象头的Mark Word会指向一个Monitor(监视器)对象。
- Monitor是JVM实现的同步原语,包含:
- Owner:持有锁的线程。
- EntryList:竞争锁失败的线程队列。
- WaitSet:调用
wait()方法释放锁后进入等待的线程队列。
重量级锁的获取与释放流程
获取锁
- 线程尝试获取锁时,若锁已被其他线程持有,会进入Monitor的EntryList并被挂起(进入内核态)。
- 锁释放后,操作系统唤醒EntryList中的线程,竞争锁(再次进入用户态)。
释放锁
- 持有锁的线程执行完同步块后,释放Monitor的Owner权限。
- 通过操作系统唤醒EntryList中的等待线程。
重量级锁的性能特点
优点
- 提供强线程安全性,适用于高竞争场景。
- 不会因CAS操作频繁失败而导致性能骤降。
缺点
- 用户态与内核态切换开销大:每次线程阻塞和唤醒都需要通过操作系统,耗时约为几微秒到几十微秒。
- 上下文切换频繁:竞争激烈时会导致大量线程频繁阻塞和唤醒。
重量级锁与轻量级锁的对比
| 特性 | 重量级锁 | 轻量级锁 |
|---|---|---|
| 实现方式 | 操作系统互斥量(内核态) | CAS操作(用户态) |
| 适用场景 | 高竞争场景 | 低竞争或无竞争场景 |
| 线程状态 | 阻塞(内核态) | 自旋(用户态) |
| 性能开销 | 高(上下文切换) | 低(CAS失败后可能升级) |
| 对象头Mark Word | 指向Monitor对象 | 指向线程栈帧中的锁记录 |
重量级锁的触发条件
- 锁升级:当轻量级锁的CAS操作频繁失败(多线程同时竞争)时,锁会升级为重量级锁。
- 调用
wait()/notify():这些方法依赖Monitor机制,会强制锁升级为重量级锁。
代码示例
以下Java代码演示了重量级锁的典型使用场景:
public class HeavyweightLockExample {
private static final Object lock = new Object();
public static void main(String[] args) {
// 创建多个线程竞争同一锁
for (int i = 0; i < 5; i++) {
new Thread(() -> {
while (true) {
synchronized (lock) {
try {
// 模拟耗时操作,增加锁竞争
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " acquired the lock");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "Thread-" + i).start();
}
}
}
在这个示例中:
- 多个线程频繁竞争同一锁,轻量级锁会迅速升级为重量级锁。
- 线程在获取锁失败后会被阻塞,进入Monitor的EntryList。
优化建议
- 减少锁的粒度:避免在大同步块中执行耗时操作,缩小锁的范围。
- 使用更高效的并发工具:如
ReentrantLock、ConcurrentHashMap等,它们在高竞争下性能更优。 - 避免锁竞争:通过线程本地存储(ThreadLocal)或无锁算法(如CAS)减少对锁的依赖。
总结
重量级锁是Java同步机制的基础,提供了可靠的线程安全保障,但由于依赖操作系统内核,性能开销较大。现代JVM通过锁升级机制(偏向锁→轻量级锁→重量级锁)平衡了安全性与性能,开发者应根据实际场景选择合适的同步策略。