JVM 内置锁 synchronized 的几种状态概述(下)

简介: JVM 内置锁 synchronized 的几种状态概述

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 。


image.png


偏向锁偏向状态跟踪


下面代码主要演示了对象从无锁,到偏向锁的过程。代码如下:


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());


打印结果如下:


image.png


偏向锁状态调用  hashcode 方法


调用锁对兑现的 obj.hashCode() 或者 System.identityHashCode(obj) 方法时候会导致该对象的偏向锁被撤销。因为一个对象,其 hashcode 只会被生成一次并且保存,偏向锁是没有地方存储  hashcode 的


  • 轻量级锁会在锁记录中记录 hashCode


  • 重量级锁会在 Monitor 中记录 hashCode


当对象处于可偏向(也就是线程 ID 为0)和已偏向的状态下,调用 hashCode 计算会将使对象也无法偏向:


  • 当对象可偏向时, MarkWord 将编程未锁定状态,并且只能升级成轻量级锁


  • 当对象正处于偏向锁时, 调用 hashCode 使偏向锁强制升级为重量级锁


实验代码:


image.png


偏向锁内调用,共享对象 obj1 的 hashCode 方法,锁被升级为重量级锁。


image.png


偏向锁状态调用 wait/notify


偏向锁状态执行 obj.notify 会升级为轻量级锁。


image.png


调用 obj.wait(timeout) 会升级为重量级锁。


image.png


轻量级锁状态


如果偏向锁失败, 虚拟机并不会升级为重量级锁,它还会尝试使用一种称为轻量锁的优化手段,此时 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());
    }
}


打印日志:


image.png


由于日志过长无法标记,我就简单的画一个流程图吧


image.png


轻量级锁升级为重量级锁


需要制造竞争激烈的场景,我们可以通过线程池的方式来模拟,代码如下:


@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());
    }
}


我们再来看看结果: 第一个线程获取锁过后,锁由偏向锁升级为轻量级锁


image.png


第二个线程已经采用的是重量级锁。


image.png


说明:为什么有时候没有升级成重量级锁?这个可能是当是 CPU 资源比较空闲,计算逻辑处理能力比较强,不需要进行锁升级,大家可以尝试多增加几个线程参与锁的争抢或者该程序多运行几次。


锁的状态转化


偏向锁、轻量级锁的状态转化及对象 Mark Word 的关系转换入下图所示:


image.png


参考资料





相关文章
|
5月前
|
存储 监控 安全
Java虚拟机的锁优化策略
Java虚拟机的锁优化策略
28 0
|
3月前
|
Java 应用服务中间件
深入理解JVM - 类加载器概述
深入理解JVM - 类加载器概述
19 0
|
10月前
|
算法 Java UED
JVM之垃圾回收器概述
JVM之垃圾回收器概述
|
5月前
|
消息中间件 算法 Java
三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
年末离职,年初为面试也筹备挺长一段时间,找了不少复习资料,刷了很多题在网上投了很多简历最终面试了有赞,还有幸拿到offer!
|
8月前
|
安全 前端开发 Java
JVM概述和类加载子系统
我记得当年学java的时候,就很好奇,为什么我在IDEA上写一些代码(其实就是一堆我们人能知道的英文单词的组合加一些运算符),为什么就可以在windows上运行后执行我们的指令,而且还可以打成jar包去linux系统跑起来,为什么一份代码可以在不同平台运行呢?类是如何加载的?对象如何创建的以及都有哪些信息?我创建的对象被分配到哪个内存去了?java是怎么和我们操作系统打交道的又是怎么调用CPU为我们计算的?创建了对象分配了内存,为什么可以不用手动回收就可以自动清理内存等等等,相信你也同样有过这些困惑。
57 0
|
8月前
|
缓存 Java 大数据
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
94 0
|
8月前
|
安全 前端开发 Java
JVM(一)JVM概述
JVM(一)JVM概述
40 0
|
9月前
|
消息中间件 算法 Java
三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
年末离职,年初为面试也筹备挺长一段时间,找了不少复习资料,刷了很多题在网上投了很多简历最终面试了有赞,还有幸拿到offer!
|
9月前
|
缓存 算法 NoSQL
史上最全499道Java面试题:JVM+分布式+算法+锁+MQ+微服务+数据库
JAVA中的几种基本数据类型是什么,各自占用多少字节。 String类能被继承吗,为什么。 String,Stringbuffer,StringBuilder的区别。 ArrayList和LinkedList有什么区别。 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。 用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
|
10月前
|
监控 算法 Java
jvm之垃圾回收概述解读
jvm之垃圾回收概述解读