偏向锁(Biased Locking)是一种优化 Java synchronized
锁的机制,旨在减少在无竞争情况下的锁开销。它通过将锁偏向于单个线程来优化锁的性能。以下是偏向锁减少锁开销的具体方式和原理:
偏向锁的工作原理
-
锁的初始状态:
- 当一个对象的锁首次被持有时,JVM 会将该对象的锁标记为偏向锁,并将对象的头部中的 Mark Word(对象头的一部分)设置为偏向于当前线程。此时,只有这个线程可以获得锁,而其他线程则不能获取。
-
偏向锁的记录:
- 对象头中的 Mark Word: 偏向锁的状态保存在对象头的 Mark Word 中。Mark Word 中会保存持有锁的线程 ID。如果锁的持有者线程进行锁操作,那么在该线程访问该锁对象时,JVM 可以快速检查到该线程已经持有锁,无需进行额外的同步操作。
-
锁的获取和释放:
- 获取锁: 当线程尝试获取锁时,JVM 会检查对象头中的 Mark Word。如果 Mark Word 中记录的线程 ID 与当前线程匹配,则说明当前线程已经持有锁,此时锁的获取是“无成本”的。
- 释放锁: 当持有锁的线程释放锁时,JVM 会将对象头中的 Mark Word 恢复为初始状态,使得其他线程可以重新获取锁。
-
撤销偏向锁:
- 锁竞争: 如果偏向锁的持有者线程在持有锁期间被中断或其他线程尝试获取该锁,那么偏向锁会被撤销。此时,锁会升级为轻量级锁(Lightweight Locking),并且偏向锁的记录会被移除。
偏向锁减少锁开销的方式
-
减少同步操作的开销:
- 减少标记和检查: 偏向锁在没有竞争的情况下,锁的获取和释放不会进行额外的同步操作。通过直接检查对象头中的 Mark Word,JVM 能够快速判断是否需要进行加锁操作。
-
避免锁的重入:
- 线程 ID 的记录: 偏向锁记录了持有锁的线程 ID,当持有锁的线程再次尝试获取锁时,可以快速确认锁已经被持有,无需进行实际的加锁和解锁操作,从而减少了锁的开销。
-
减少线程调度开销:
- 避免上下文切换: 偏向锁在没有竞争的情况下,避免了线程之间的上下文切换,从而减少了由于线程切换导致的性能开销。
偏向锁的局限性
-
锁竞争:
- 竞争撤销: 如果有多个线程同时竞争一个偏向锁,偏向锁会被撤销并升级为轻量级锁,这会引入额外的开销。此时,锁的优化效果可能会减少。
-
长时间持有:
- 长时间持有偏向锁: 如果线程长时间持有偏向锁而没有竞争,虽然可以减少锁的开销,但在线程进行操作时,偏向锁的持有者需要保持对象头中的线程 ID,这可能会导致一些内存开销。
示例代码
以下示例演示了如何触发偏向锁的机制:
public class BiasedLockExample {
private static final Object lock = new Object();
public static void main(String[] args) {
// 创建多个线程来访问同步方法
Runnable task = () -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " - Acquired lock");
try {
Thread.sleep(100); // 模拟工作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 启动多个线程来测试偏向锁的效果
for (int i = 0; i < 5; i++) {
new Thread(task, "Thread-" + i).start();
}
}
}
在这段代码中,synchronized
方法的对象 lock
初始会使用偏向锁。随着线程的竞争,JVM 会将偏向锁升级为轻量级锁或重量级锁(如有必要)。
总结
- 偏向锁: 主要用于减少在没有锁竞争时的开销。
- 对象头中的 Mark Word: 用于记录持有锁的线程 ID,从而优化锁的获取和释放。
- 锁竞争和撤销: 当检测到锁竞争时,偏向锁会被撤销并升级为轻量级锁,优化锁的性能。
偏向锁通过在没有竞争的情况下减少同步开销,从而提高程序的执行效率。