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


参考资料





相关文章
|
6月前
|
存储 监控 安全
Java虚拟机的锁优化策略
Java虚拟机的锁优化策略
59 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
4月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
49 0
|
1月前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
41 1
|
1月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
6月前
|
Java 应用服务中间件
深入理解JVM - 类加载器概述
深入理解JVM - 类加载器概述
56 0
|
6月前
|
消息中间件 算法 Java
三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
年末离职,年初为面试也筹备挺长一段时间,找了不少复习资料,刷了很多题在网上投了很多简历最终面试了有赞,还有幸拿到offer!
|
安全 前端开发 Java
JVM概述和类加载子系统
我记得当年学java的时候,就很好奇,为什么我在IDEA上写一些代码(其实就是一堆我们人能知道的英文单词的组合加一些运算符),为什么就可以在windows上运行后执行我们的指令,而且还可以打成jar包去linux系统跑起来,为什么一份代码可以在不同平台运行呢?类是如何加载的?对象如何创建的以及都有哪些信息?我创建的对象被分配到哪个内存去了?java是怎么和我们操作系统打交道的又是怎么调用CPU为我们计算的?创建了对象分配了内存,为什么可以不用手动回收就可以自动清理内存等等等,相信你也同样有过这些困惑。
81 0
|
缓存 Java 大数据
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
223 0
|
安全 前端开发 Java
JVM(一)JVM概述
JVM(一)JVM概述
62 0