深入理解synchronized 关键字

锁的对象

  • this 对象,当前对象
  • 指定共享对象
  • Class 对象,XXX.class 全局唯一的对象,是java.lang.Class 的实例
    • 对静态方法加锁也是Class 对象实例进行加锁

锁在方法上,为this 锁,静态方法为全局Class 锁

原理

JVM 层面提供一个minitor 锁的机制,对使用了synchronized 关键字的代码块模块加上了monitorenter 指令和monitorexit 指令。

typescript 复制代码
public class Demo {
    public  void foo(){
        synchronized(this) {
             System.out.println("Hello World!");
        }
    }
    public static void main(String[]args) {
        Demo demo = new Demo();
        demo.foo();
    }
}

使用javap -verbose 命令可以得到反编译的字节码,能看到monitorenter 和monitorexit 指令,如下所示

对于使用synchronized 关键字修饰的方法,JVM 并不会像代码块一样加上monitorenter 和monitorexit 方法,而是在方法的flags 处加上 ACC_SYNCHRONIZED 标志。

当JVM 调用方法时识别到有这个标记时,判断是否为静态方法,根据方法的类型对不同的对象加索(实例/Class 实例) 。保证同步代码块的安全性。

如果JVM 识别到这个同步方法实际并不存在竟态情况的时候,会进行锁消除等优化。

typescript 复制代码
public class Demo {

    public  synchronized void foo(){
     System.out.println("Hello World!");
    }
    public static void main(String[]args) {
        Demo demo = new Demo();
        demo.foo();
    }
}

反编译字节码如下

可重入锁

又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。

根据锁的级别,可重入锁的依据也不同,取决于锁实例的对象头,根据锁的等级实现的原理也不一致。

  • 偏向锁是根据对象头存储的偏向的线程id,锁重入的时候,markword 里面的线程id 是不会发生任何变化的
  • 轻量级锁根据markword 中存储的指向lock record 的指针,在重入的时候也是没有变化的
  • 重量级锁,在重入的时候,指向的object monitor 里面有一个_recursion 字段是会进行递增的

happens-before 原则

根据JMM 的monitor lock rule 原则,释放锁的时候会强制刷新工作内存到主内存,加锁的时候也强制要求从主内存重新加载数据

锁优化方法

JDK 1.6 在JVM 层面对锁进行了优化。根据实时情况控制锁的粒度

  • 锁粗化:识别到同时操作多个同步操作,一把锁,直接优化成一把锁,而不是多把锁
  • 锁消除: 根据JIT + 逃逸分析,直接把锁干掉
  • 适应性自旋
  • 偏向锁
  • 轻量级锁

锁的类型

  • 无锁 => 偏向锁 => 轻量级锁 => 重量级锁

自旋锁:

  • JDK定义中,自旋锁默认的自旋次数为10次,用户可以使用参数-XX:PreBlockSpin来更改。(jdk 1.6 之前,现已弃用,默认位自适应自旋锁)

自适应自旋锁

  • 根据上一个线程等待的时间和次数来决定当前线程自旋等待的时间和次数

锁消除

  • 基于JIT 逃逸分析,认定加了锁的代码实际上并没有race condition ,会把锁进行消除
  • jdk 1.5 后,stringbuffer 如果没有race condition 则会编译为string builder

轻量级锁和偏向锁

  • 基于对象头中的markword 作为共享变量来实现,jvm 层级的cas 指令判断当前对象锁被哪个线程占用。
  • 拓展:JVM 对象头

锁升级

锁升级的主要体现在对象头的markword 上,markword是一个动态的对象

  • 无锁时 存储hashcode + 锁标记
  • 偏向锁时存储偏向锁线程id,如果已经计算过hash code 则无法使用偏向锁
  • 轻量级锁 存储Lock record 地址,Lock record 中会复制一份markword,如果在升级为轻量级锁之前使用了hashcode方法,则hashcode 则会在里面,如果没有那么hashcode 生成但需要调用,锁就会升级为重量级锁
  • 重量级锁:生成object monitor 对象,需要进行内核到用户态的切换,markword 存储object monitor 的位置,生成的object monitor 会复制一份旧的markword对象

juejin.cn/post/697888...

上述标志位可以使用JOL 工具进行观察得到

scss 复制代码
package org.example;

import org.openjdk.jol.info.ClassLayout;

public class SynchronizedLockUpgradeDemo {
    public static void main(String[] args) throws InterruptedException {
        //创建锁对象
        Object lock = new Object();
        //JVM 存在偏向锁启动延迟,延迟几秒,或者通过-XX:BiasedLockingStartupDelay=0 禁用
        Thread.sleep(5000);

        System.out.println("================ 1. 初始状态:无锁 ================");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        System.out.println("================ 2. 锁升级为偏向锁 ================");
        synchronized (lock) {
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        }
        System.out.println("\n退出同步块后,锁对象依然保持偏向锁状态,偏向于主线程");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        System.out.println("================ 3. 锁升级为轻量级锁 ================");
        Thread thread = new Thread(() -> {
            synchronized (lock) {
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
            }
        });
        thread.start();
        thread.join();

        System.out.println("================ 4. 锁升级为重量级锁 ================");
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread3 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock) {
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
            }
        });
        thread2.start();
        thread3.start();
        thread2.join();
        thread3.join();
        System.out.println("\n================ 5. 重量级锁释放后 ================");
        System.out.println("所有线程执行完毕后,锁被释放,但可能不会立即变回无锁状态");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());


    }

}

标志位变化过程:从无锁(可偏向 101 ) => 偏向锁 (101) => 轻量级锁(00) => 重量级锁 (10)

输出如下

yaml 复制代码
"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2025.1.2\lib\idea_rt.jar=64753" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;F:\code\source\target\classes;C:\Users\A80236.m2\repository\org\openjdk\jol\jol-core\0.17\jol-core-0.17.jar" org.example.SynchronizedLockUpgradeDemo
================ 1. 初始状态:无锁 ================
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================ 2. 锁升级为偏向锁 ================
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000003803005 (biased: 0x000000000000e00c; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


退出同步块后,锁对象依然保持偏向锁状态,偏向于主线程
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000003803005 (biased: 0x000000000000e00c; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================ 3. 锁升级为轻量级锁 ================
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000035a1f210 (thin lock: 0x0000000035a1f210)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

================ 4. 锁升级为重量级锁 ================
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000033be1c8a (fat lock: 0x0000000033be1c8a)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


================ 5. 重量级锁释放后 ================
所有线程执行完毕后,锁被释放,但可能不会立即变回无锁状态
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000033be1c8a (fat lock: 0x0000000033be1c8a)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

配置

偏向锁开关

-XX:+UseBiasedLocking # 启用偏向锁(默认)

-XX:-UseBiasedLocking # 禁用偏向锁

-XX:BiasedLockingStartupDelay=0 # 禁用偏向锁延迟

相关推荐
香饽饽~、13 分钟前
[第十三篇] Spring Boot监控
java·spring boot·后端
shawya_void21 分钟前
算法:数组part02: 209. 长度最小的子数组 + 59.螺旋矩阵II + 代码随想录补充58.区间和 + 44. 开发商购买土地
java
javadaydayup24 分钟前
Java注解底层竟然是个Map?
java
火山锅2 小时前
🚀 Spring Boot枚举转换新突破:编译时处理+零配置,彻底告别手写转换代码
java·架构
秋千码途2 小时前
小架构step系列25:错误码
java·架构
RealmElysia2 小时前
SpringCache
java·spring·bootstrap
编写美好前程2 小时前
springboot项目如何写出优雅的service?
java·spring boot·后端
Java&Develop3 小时前
Java中给List<String>去重的4种方式
java·windows·list
荒诞硬汉3 小时前
数组相关学习
java·学习
hqxstudying3 小时前
J2EE模式---业务代表模式
java·前端·python·设计模式·java-ee·mvc