JVM 参数:
//关闭延迟开启偏向锁 ‐XX:BiasedLockingStartupDelay=0 //禁止偏向锁 ‐XX:‐UseBiasedLocking //启用偏向锁 ‐XX:+UseBiasedLocking
测试代码(注意,这里需要注意的是需要创建两个对象,因为对象头信息的初始化是在 new
关键字执行的时候初始化,笔者之前就遇到了这样的问题,导致实验失败):
public class BiasedLockDelayTest { public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); System.out.println(ClassLayout.parseInstance(obj1).toPrintable()); System.out.println(); Thread.sleep(5000); Object obj2 = new Object(); System.out.println(ClassLayout.parseInstance(obj2).toPrintable()); } }
结果打印如下,延迟 5 秒后新创建的都默认开启匿名偏向, threadid = 0 。
偏向锁偏向状态跟踪
下面代码主要演示了对象从无锁,到偏向锁的过程。代码如下:
log.debug(ClassLayout.parseInstance(new Object()).toPrintable()); Thread.sleep(4000); Object obj1 = new Object(); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); new Thread(new Runnable() { @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread0").start(); Thread.sleep(5000); log.debug(ClassLayout.parseInstance(obj1).toPrintable());
打印结果如下:
偏向锁状态调用 hashcode 方法
调用锁对兑现的 obj.hashCode() 或者 System.identityHashCode(obj) 方法时候会导致该对象的偏向锁被撤销。因为一个对象,其 hashcode 只会被生成一次并且保存,偏向锁是没有地方存储 hashcode 的
- 轻量级锁会在锁记录中记录 hashCode
- 重量级锁会在 Monitor 中记录 hashCode
当对象处于可偏向(也就是线程 ID 为0)和已偏向的状态下,调用 hashCode 计算会将使对象也无法偏向:
- 当对象可偏向时, MarkWord 将编程未锁定状态,并且只能升级成轻量级锁
- 当对象正处于偏向锁时, 调用 hashCode 使偏向锁强制升级为重量级锁
实验代码:
偏向锁内调用,共享对象 obj1
的 hashCode 方法,锁被升级为重量级锁。
偏向锁状态调用 wait/notify
偏向锁状态执行 obj.notify 会升级为轻量级锁。
调用 obj.wait(timeout) 会升级为重量级锁。
轻量级锁状态
如果偏向锁失败, 虚拟机并不会升级为重量级锁,它还会尝试使用一种称为轻量锁的优化手段,此时 Mark Word 的结构也会变成轻量级锁的结构,轻量级锁所适应的场景就是线程交替执行同步块的场合,如果存在同一个时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。
偏向锁升级为轻量级锁
@Slf4j public class LightweightLockTest { public static void main(String[] args) throws InterruptedException { log.debug(ClassLayout.parseInstance(new Object()).toPrintable()); Thread.sleep(5000); Object obj1 = new Object(); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread0").start(); Thread.sleep(1000); new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread1").start(); Thread.sleep(5000); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); } }
打印日志:
由于日志过长无法标记,我就简单的画一个流程图吧
轻量级锁升级为重量级锁
需要制造竞争激烈的场景,我们可以通过线程池的方式来模拟,代码如下:
@Slf4j public class LightweightLockTest { public static void main(String[] args) throws InterruptedException { log.debug(ClassLayout.parseInstance(new Object()).toPrintable()); Thread.sleep(5000); Object obj1 = new Object(); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); ExecutorService executeService = Executors.newFixedThreadPool(2); executeService.submit(new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread0")); //Thread.sleep(1000); executeService.submit(new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread1")); Thread.sleep(5000); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); } }
我们再来看看结果: 第一个线程获取锁过后,锁由偏向锁升级为轻量级锁
第二个线程已经采用的是重量级锁。
�说明:为什么有时候没有升级成重量级锁?这个可能是当是 CPU 资源比较空闲,计算逻辑处理能力比较强,不需要进行锁升级,大家可以尝试多增加几个线程参与锁的争抢或者该程序多运行几次。
锁的状态转化
偏向锁、轻量级锁的状态转化及对象 Mark Word 的关系转换入下图所示: