引言: 偏向锁在JVM中是一个轻量级锁,本文将分析其原理、应用场景以及在不同的JDK场景下的性能差异,从而让我们对其有更深的理解。
1. 偏向锁(Biased Lock)
偏向锁的目的是为了在无锁竞争的情况下避免在锁获取过程中执行不必要的CAS原子指令;现有的CAS原子指令虽然相对于重量级锁来说开销比较小但还是存在非常可观的本地延迟。而偏向锁则针对拥有当前锁的线程,允许其在竞争不存在的情况下,直接进入同步的代码块,无需同步操作,从而获取了相当的性能提升。
2. 原理分析
偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。 其实现的细节如下:
- 将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark
- 只要没有竞争,获得偏向锁的线程,在将来进入同步块,不需要做同步
- 当其他线程请求相同的锁时,偏向模式结束
何为对象头?
在JVM中创建对象时会在对象前面加上两个字大小的对象头,在32位机器上一个字为32bit,根据不同的状态位Mark World中存放不同的内容,如上图所示在轻量级锁中,Mark Word被分成两部分,刚开始时LockWord为被设置为HashCode、最低三位表示LockWord所处的状态,初始状态为001表示无锁状态。Klass ptr指向Class字节码在虚拟机内部的对象表示的地址。Fields表示连续的对象实例字段。
我们这里只需要关心biasable和lightweight locked两种状态。在JDK1.6以后默认已经开启了偏向锁这个优化,我们可以通过在启动JVM的时候加上-XX:-UseBiasedLocking参数来禁用偏向锁(在存在大量锁对象的创建并高度并发的环境下禁用偏向锁能够带来一定的性能优化)
3. 应用场景
在偏向锁的应用场景主要集中在竞争不激烈的情况下,通过使用偏向锁可以减少其在CAS操作下的同步性能消耗,从而获取性能的提升。关键点在于是否存在激烈的锁竞争,如果存在则不适合使用它。当然了默认情况下,其实被打开的。
4. 测试代码分析
测试使用的代码:
import java.util.List;
import java.util.Vector;
public class NonBiasedLockTest {
public static List<Integer> numberList = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
long begin = System.currentTimeMillis();
int count = 0;
int startnum = 0;
while (count < 10000000) {
numberList.add(startnum);
startnum += 2;
count++;
}
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
}
测试的情况分析(测试的硬件环境为笔者个人电脑):
Case 1:
JDK 1.8.0_101
JVM Opts: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
Time consumed: 1845 ms
Case 2:
JDK 1.8.0_101
JVM Opts: -XX:-UseBiasedLocking
Time consumed: 2063 ms
Case 3:
JDK 1.7.0_79
JVM Opts: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
Time consumed: 3916 ms
Case 4:
JDK 1.7.0_79
JVM Opts: -XX:-UseBiasedLocking
Time consumed: 4149 ms
5. 结论分析
在JDK 8的情况下,使用偏向锁性能提升大约220ms左右,提升量大约10%; 在JDk7的同等情况下, 使用偏向锁性能提升大约230ms,提升量大约5%。
同样的设置,在JDK7和JDK8之下,在JDK8之下的性能提升了将近100%,JDK版本的升级对于代码性能的提升还是非常显著的。