偏向锁是jvm为了消除无竞争状态下同步原语开销而引入的一种优化机制,其核心做法是让第一个获取锁的线程在对象头中记录自己的id,后续该线程再次进入时只需检查id是否匹配,无需执行cas操作。具体实现依赖于jvm启动参数中的-xx:+usebiasedlocking(java 15后默认关闭),当多个线程交替访问同一资源且无竞争时,它能显著减少上下文切换和cpu指令消耗。

偏向锁如何识别并锁定当前线程
偏向锁通过修改对象头中的Mark Word来记录持有锁的线程ID,从而避免后续的加锁动作。在Java 8及早期版本中,这是默认的优化手段。当线程A第一次访问同步块时,JVM会使用CAS操作将对象头中的Thread ID设置为A的ID,并设置偏向标志位为1。此后,只要没有其他线程尝试竞争这把锁,线程A再次进入该同步块时,JVM只需比对当前线程ID与对象头中的ID是否一致,若一致则直接继续执行,整个过程不涉及任何原子操作或系统调用。

public class BiasedLockDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
// 模拟偏向锁生效:单线程反复获取锁
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (lock) {
// 临界区代码
}
}
}, "T1");
// 为了让偏向锁生效,需要等待一段时间让JVM完成批量重偏向
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Single thread execution completed.");
}
}上述代码展示了单线程场景下偏向锁的行为。由于只有一个线程T1不断获取同一个对象的锁,JVM会在首次获取时进行偏向操作,之后的9999次获取都将跳过CAS比较,直接获得锁。这证明了在无竞争环境下,偏向锁确实消除了同步开销。注意,如果此时启动第二个线程T2去竞争同一个锁,偏向锁会被撤销,并升级为轻量级锁。
立即学习“Java免费学习笔记(深入)”;
偏向锁在遇到竞争时的升级过程
当有其他线程尝试获取已被偏向上的锁时,偏向锁机制会失效,锁状态会升级为轻量级锁。这一过程涉及全局安全点(Safepoint)的暂停和Mark Word的修改。一旦检测到竞争,JVM会停止所有拥有该偏向锁的线程,并将对象头中的Thread ID清空,恢复为正常的锁标记。随后,新竞争的线程会通过CAS操作尝试获取锁,如果成功,则进入轻量级锁阶段;如果失败,则可能进一步膨胀为重量级锁。

public class LockUpgradeDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 第一阶段:单线程偏向
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("T1 holds biased lock");
}
});
t1.start();
t1.join();
// 第二阶段:引入竞争,触发升级
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("T2 competes for lock");
}
});
// 短暂延迟确保t1已完成偏向
Thread.sleep(100);
t2.start();
t2.join();
System.out.println("Lock upgraded to lightweight or heavier.");
}
}在这个例子中,T1首先获取了偏向锁。当T1结束后,T2开始竞争。由于对象已经被偏向于T1,T2的CAS操作会失败(或者JVM在检测到竞争后主动撤销偏向)。此时,锁的状态不再保持偏向,而是根据竞争情况升级为轻量级锁(自旋)或重量级锁(OS Mutex)。开发者可以通过-XX:-UseBiasedLocking禁用此功能,强制使用轻量级或重量级锁,这在某些高并发多核场景下可能更稳定,因为偏向锁的撤销成本较高。
为什么Java 15默认关闭了偏向锁
基于程序中偏向锁撤销的具体行为,我们发现其在多核高并发环境下可能导致性能下降而非提升。偏向锁的设计初衷是优化单线程或极少竞争的场景,但在现代服务器应用中,多线程共享资源的竞争较为常见。一旦竞争发生,JVM必须暂停相关线程以撤销偏向,这个“全局安全点”操作带来了显著的延迟。此外,偏向锁的撤销逻辑复杂,且在许多实际基准测试中,其带来的收益往往被撤销成本抵消。因此,从Java 15开始,Oracle决定默认关闭偏向锁,以减少不必要的复杂性并提高整体系统的可预测性。


















