Java中的偏向锁(Biased Locking)是一种针对线程竞争不激烈场景的轻量级锁优化措施,它在Java 6中首次引入。当锁大多数情况下由同一个线程多次获取时,偏向锁的目标是减少不必要的同步操作,从而提高程序性能。
工作原理:
-
初次获取锁:
- 当一个线程首次访问一个对象并获取锁时,对象头部的Mark Word会记录下当前线程的ID,将锁转变为偏向锁状态,并将其绑定到这个线程上。
- 之后,该线程在后续访问此对象时,由于锁已经偏向于它,所以可以直接绕过大部分同步操作,几乎无感知地进入同步块。
-
偏向检查:
- 持有偏向锁的线程在进入同步块时,只需简单地检查对象头中存储的线程ID是否仍为其自己的ID,如果是,则无需执行加锁操作就能继续执行。
-
撤销偏向锁:
- 当其他线程尝试获取这个已被偏向的锁时,原有的偏向锁需要被撤销。
- 撤销过程包括挂起持有偏向锁的线程,然后将偏向锁升级为轻量级锁甚至重量级锁,以公平地满足其他线程的竞争要求。
优点:
- 减少无竞争情况下的同步操作,降低锁获取和释放的开销,提升程序性能。
适用场景:
- 偏向锁适用于线程交替执行较少且锁竞争不激烈的场合。
注意事项:
- 如果系统中线程切换频繁或者锁竞争激烈,偏向锁反而可能增加开销,因此JVM会根据运行状况动态调整锁的状态,适时地启用和禁用偏向锁。
在现实生活中,我们可以用办公室门锁来比喻Java中的偏向锁。
假设在一个办公室里,有个员工小明每天上班最早,他总是第一个到达办公室并打开门锁。为了方便,办公室经理决定实行一项新规定:如果小明连续几天都是第一个到达并且离开最后一个,那么门锁将只为小明“偏向”开启,即小明只需轻轻一推,门锁就自动解锁让他进去,而不需要每次都拿出钥匙开锁。
在Java并发编程中,偏向锁就好比这个“聪明”的门锁。当一个线程(比如小明线程)反复多次获取同一个对象锁(如同开门动作),JVM会检测到这个现象并转换成偏向锁模式。之后,每当这个线程再次试图获取这个锁时,就无需像其他线程那样经历完整的加锁过程(如CAS操作或重量级锁),而是可以直接访问同步代码块,大大提高了效率。但当有其他线程也需要争夺这把“锁”时,偏向锁就会解除,恢复到正常锁竞争机制,确保公平性。
public class BiasedLockExample {
// 假设这是需要同步的对象
private final Object monitor = new Object();
// 假设这个方法是由一个线程反复调用,也就是偏向锁的典型场景
public void methodExecutedByOneThread() {
// 当第一次进入同步代码块时,JVM会将monitor对象偏向于当前线程
// 在后续的调用中,如果仍然是同一个线程,那么:
synchronized (monitor) {
// 1. 无需进行CAS操作或重量级锁操作
// 2. 直接确认monitor对象的偏向状态,如果是偏向当前线程,则直接进入同步代码块
// 执行同步代码...
// 假设这里有对共享资源的修改操作
sharedResource++;
// ...
}
}
// 其他线程尝试获取锁时,偏向锁会自动撤销并升级为轻量级锁或重量级锁
public void methodExecutedByAnotherThread() {
synchronized (monitor) {
// 当其他线程尝试获取锁时,偏向锁会撤销并可能引起锁膨胀
// 执行同步代码...
}
}
private int sharedResource;
}
实际的偏向锁逻辑发生在JVM层面,上述Java代码并未直接体现偏向锁的实现细节,而是通过注释说明了在JVM内部处理线程同步时,偏向锁如何简化同步过程,提高执行效率。在Java 6及更高版本中,JVM默认启用偏向锁功能。在高度竞争的场景下,JVM会自动降级或撤销偏向锁,转而使用轻量级锁或重量级锁。