Java并发编程之 锁升级过程 阳光穿透心脏的1/2处 2022-11-09 15:29 160阅读 0赞 阅读本文档前需了解的前置知识: 1. 进程和线程的概念 2. 并发和并行的概念 3. 创建线程的三种方式 4. 线程常见方法 5. 线程的五种状态及其转换 # synchronized优化原理 # ### 文章目录 ### * synchronized优化原理 * * 一、Monitor介绍 * * 1. 对象头 * 2. Monitor原理 * 二、synchronized原理 * * 1. 轻量级锁 * 2. 锁膨胀 * 3. 自旋优化 * 4. 偏向锁 * * (1) 概念 * (2) 偏向状态 * (3) 偏向锁失效的情况 * * i. 调用hashcode()方法 * ii. 其他线程使用对象 * iii. 调用wait / notify()方法 ## 一、Monitor介绍 ## Monitor即通常所说的锁(重量级锁),又被称为监视器或管程,由操作系统提供 ### 1. 对象头 ### 一个Java对象的结构如下: ![20210315213104712.png][] * 普通对象的对象头 ![20210315213114166.png][] * Klass Word为指针,指向此对象所属的类,通过指针找到此对象所属的类 * 数组对象的对象头 ![\+][20210315213137949.png] * Mark Word结构 * 其中保存哈希值、分代年龄、加锁状态等信息 ![\+][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70] ### 2. Monitor原理 ### * 每个使用synchronized关键字的 Java 对象,该对象的Mark Word从无锁变为指向Monitor对象的指针,且标志位变为10 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 1][] * 刚开始时Monitor中的Owner为null,Owner表示谁是这把锁的所有者 * 当 Thread-2 执行`synchronized(obj){}`代码时就会将Monitor的所有者 Owner 设置为 Thread-2,上锁成功,Monitor中同一时刻只能有一个Owner ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 2][] * 当 Thread-2 占据锁时,如果线程 Thread-3、Thread-4、Thread-5 也来执行`synchronized(obj){}`代码,就会进入 EntryList 中变成BLOCKED状态,EntryList是阻塞队列 * Thread-2 执行完同步代码块的内容,进行解锁,Monitor的Owner变空,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的 * 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足而进入 WAITING 状态的线程 ## 二、synchronized原理 ## ### 1. 轻量级锁 ### * 使用场景 * 如果一个对象虽然有多个线程要对它进行加锁,但是加锁的时间是错开的(也就是没有人竞争),那么可以使用轻量级锁来进行优化 * 轻量级锁对使用者是透明的,即语法仍然是`synchronized` * 如果出现了多个线程竞争锁的情况,则轻量级锁升级成为重量级锁(锁膨胀) * 举例 //假设有两个方法同步块,利用同一个对象加锁 static final Object obj = new Object(); public static void method1() { synchronized( obj ) { // 同步块 A method2(); } } public static void method2() { synchronized( obj ) { // 同步块 B } } 1. 当执行到method1的synchronized代码块时,会在方法对应的栈帧中创建锁记录(Lock Record)(充当轻量级锁),每个方法对应的栈帧中都会包括一个锁记录的结构,锁记录内部可以储存要加锁对象的 Mark Word 和对象引用reference 注意:一开始锁记录并不是直接存储加锁对象的mark word,而是锁记录的地址,后期二者会交换 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 3][] 2. 让锁记录中的 Object reference 指向锁对象,并且尝试用锁记录地址替换Object对象的Mark Word,将Mark Word 的值存入锁记录中 交换的目的是为了加锁,查阅mark word得知00表示轻量级锁,01表示无锁 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 4][] 3. 如果交换成功,那么对象头储存的就是锁记录的地址和状态00(轻量级锁),表示由该线程给该对象加轻量级锁成功 解锁的时候将二者的信息还原回去 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 5][] 4. 如果交换失败,有两种失败情况 * 对象头的mark word后两位不是01,表示已经有线程持有锁,此时发生了锁竞争,进入锁膨胀阶段 * 当前线程执行了 synchronized 锁重入(当前线程又一次为同一个对象加锁,即本例的情况),那么再添加一条取值为null的锁记录,作为重入的计数,表示当前线程对此对象加了几次锁 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 6][] 注意:锁重入即使交换失败,但也会检查对象头的 mark word 尝试交换 5. 当线程退出method2的synchronized代码块的时候,如果获取的锁记录取值为 null(栈顶元素出栈),表示有重入,这时重置锁记录,将重入计数减一 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 7][] 6. 当线程退出method1的synchronized代码块的时候,如果获取的锁记录取值不为 null,则还原二者的锁记录地址和 mark word 至交换之前的状态 * 成功则解锁成功 * 失败则说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程 上述的交换过程称为CAS操作,有一定的性能损耗 ### 2. 锁膨胀 ### * 如果出现了多个线程竞争锁的情况,则轻量级锁升级成为重量级锁(锁膨胀) * 如果出现竞争锁,就会出现阻塞状态,但轻量级锁没有阻塞的说法,故需要升级成为重量级锁 * 过程如下 1. 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 8][] 2. 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程 3. 为加锁对象申请Monitor锁,让Object的 mark word 指向重量级锁Monitor的地址,后两位变成10(表示重量级锁),然后 Thread-1 进入Monitor 的 EntryList 变成阻塞状态 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 9][] 4. 当 Thread-0 退出synchronized同步块时(此时Thread-0还是持有轻量级锁,但加锁对象已升级成为重量级锁),会还原锁记录地址和 mark word 至交换之前的状态,失败;那么会进入重量级锁的解锁过程,即按照Monitor的地址找到Monitor对象,将Owner设置为null,唤醒 EntryList 中的 Thread-1 线程 ### 3. 自旋优化 ### * 自旋概念 * 重量级锁竞争的时候,如果 Owner 中已经有线程了,当前要加锁的线程不会立即进入 EntryList 阻塞,而是进行几次循环 * 如果当前线程自旋成功(即在自旋的时候持锁的线程释放了锁),那么当前线程即可避免阻塞 ,成功加锁 * 如果自旋失败(即自旋了一定次数还是没有等到持锁的线程释放锁),那么当前线程会进入阻塞状态 * 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋 * Java 7 之后不能控制是否开启自旋功能 ### 4. 偏向锁 ### #### (1) 概念 #### * 出现原因 * 在轻量级锁中,如果同一个线程对同一个对象进行锁重入时,也需要执行CAS操作(尽管会交换失败),CAS操作会耗时 * 偏向锁概念 * Java 6 引入了偏向锁,第一次进行CAS操作时将线程ID设置到对象头的 mark word 中(mark word 中不再存储重量级锁或轻量级锁的地址),之后这个线程再进行锁重入时,检查线程ID,发现线程ID是自己的,那么就不用再进行CAS操作了 * 偏向锁字面理解 * 加锁对象偏向(属于)此线程 #### (2) 偏向状态 #### * 偏向状态为Biased ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 10][] * 一个 Java 对象的创建过程 * 偏向锁默认是开启的,对象创建之后,mark word 最后三位的值为101(偏向状态),并且这时它的Thread,epoch,age 都是0,在加锁的时候设置这些值 * 即使偏向锁默认是开启的,但偏向锁默认是延迟开启的(不会在程序启动的时候立刻生效),也就是说如果在程序启动后立即创建对象 ,则此对象的后三位是001(无锁);如果想避免延迟,可以添加虚拟机参数来禁用延迟:`-XX:BiasedLockingStartupDelay=0` ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 11] * 处于偏向锁的对象解锁后,线程 id 仍存储于对象头中,表示此对象偏向于此线程,只有其他线程再调用此对象时,才会修改线程id * 禁用偏向锁 * 添加 VM 参数 `-XX:-UseBiasedLocking` 禁用偏向锁(禁用偏向锁则优先使用轻量级锁) * 如果没有开启偏向锁,那么对象创建后最后三位的值为001(无锁) * 轻量级锁加锁状态位为00,解锁状态位变回01 #### (3) 偏向锁失效的情况 #### ##### i. 调用hashcode()方法 ##### * 当调用对象的`hashcode()`方法的时候就会禁用这个对象的偏向锁,成为无锁状态 * 哈希值默认为0,只有第一次调用此对象的`hashcode()`才会生成哈希值并且填充到对象头的mark word中 * 原因 * 查偏向状态图得知偏向状态的mark word中没有位置存放哈希值 * 轻量级锁和重量级锁没有此特点,二者都可以存放哈希值,轻量级锁存放在锁记录中,重量级锁存放在Monitor对象中,解锁的时候会还原到对象中 ##### ii. 其他线程使用对象 ##### * 当有线程使用已经有其他线程ID的偏向锁对象时(非竞争,不同时间使用),会将偏向锁升级为轻量级锁 * 轻量级锁的使用条件是没有线程对同一个对象进行锁竞争,一旦竞争会成为重量级锁 ##### iii. 调用wait / notify()方法 ##### * 调用wait / notify()方法会使对象的锁变成重量级锁,因为wait/notify方法只有重量级锁才支持 [20210315213104712.png]: /images/20221023/00c760849cec4884b0e706550f6df141.png [20210315213114166.png]: /images/20221023/a2898ee3967f44d899bbecb5e9560865.png [20210315213137949.png]: /images/20221023/2b05eb890792457a949b5c795ea029a5.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70]: /images/20221023/aa5be1f0e2b948bf953585afca5f87ed.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 1]: /images/20221023/0676e6dc85d344f8b1b9952cc3722edd.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 2]: /images/20221023/6a45da049adb46eaa2d3c5e33a4277d3.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 3]: /images/20221023/1ba50c5665764e5f8d2b9f1d72832c38.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 4]: /images/20221023/1e66ffc0759541b7bcdae06d405448ad.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 5]: /images/20221023/92f4c32cdf6c431da78c607004c54bf1.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 6]: /images/20221023/17b59f9507e6406f9613d1c9bff915e7.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 7]: /images/20221023/945cc5d4526344418611ae04855b0128.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 8]: /images/20221023/2c7351dcfd594d7eaa0c03df3f750e16.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 9]: /images/20221023/c7c837ef1c9f4ce5bffda1e88111dfd1.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 10]: /images/20221023/c65aad763501443589833131a684e3c0.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80OTM0MzE5MA_size_16_color_FFFFFF_t_70 11]: /images/20221023/7a205c992f2842818d29828723370c93.png
相关 Java并发编程中锁升级和死锁的实例展示 在Java并发编程中,锁升级和死锁是两个重要的概念。下面我将通过具体的实例来解释这两者。 1. 锁升级: 简单来说,锁升级就是从低级锁升级到高级锁的过程。这通常发生在多 谁借莪1个温暖的怀抱¢/ 2024年09月16日 06:04/ 0 赞/ 18 阅读
相关 Java并发编程中锁升级及性能分析 在Java的并发编程中,锁是一种同步工具,用于控制对共享资源的访问。锁升级是锁级别的提升过程。 1. 锁升级: - 原子锁升级为轻量级互斥锁(Lightweight L 柔光的暖阳◎/ 2024年09月12日 04:12/ 0 赞/ 29 阅读
相关 Java并发编程中的锁升级问题 在Java的并发编程中,锁升级(Lock Involvement)是一个需要注意的问题。通常在多线程环境中,有以下几种类型的锁: 1. `synchronized`块/方法: Love The Way You Lie/ 2024年09月11日 21:36/ 0 赞/ 18 阅读
相关 【锁升级过程】 锁升级是指从轻量级锁升级到重量级锁的过程。在Java中,锁可以分为无锁、偏向锁、轻量级锁和重量级锁四种状态,它们的状态转换通常是隐式的,即由JVM自动完成的。下面是轻量级锁和重 水深无声/ 2024年03月26日 13:57/ 0 赞/ 35 阅读
相关 Java并发编程之 无锁(CAS) 共享模型之无锁 文章目录 共享模型之无锁 一、CAS 与 volatile 1. CAS 2. vola 深藏阁楼爱情的钟/ 2022年11月13日 11:20/ 0 赞/ 276 阅读
相关 Java并发编程之 锁升级过程 阅读本文档前需了解的前置知识: 1. 进程和线程的概念 2. 并发和并行的概念 3. 创建线程的三种方式 4. 线程常见方法 5. 线程的五种状态及其转换 sy 阳光穿透心脏的1/2处/ 2022年11月09日 15:29/ 0 赞/ 161 阅读
相关 Java并发编程之读写锁 读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。只要没有writer,读取锁可以由多个reader线程同时保持。写入锁是独占的。 可重入读写锁 Reentra 曾经终败给现在/ 2022年06月12日 07:55/ 0 赞/ 232 阅读
相关 Java并发编程之重入锁 重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问 刺骨的言语ヽ痛彻心扉/ 2022年06月12日 07:08/ 0 赞/ 240 阅读
相关 并发编程之 Java 三把锁 ![三把锁][1240] 前言 今天我们继续学习并发。在之前我们学习了 JMM 的知识,知道了在并发编程中,为了保证线程的安全性,需要保证线程的原子性,可见性,有序性。 逃离我推掉我的手/ 2022年06月02日 04:45/ 0 赞/ 175 阅读
还没有评论,来说两句吧...