Java的synchronized:深入底层,解锁高阶玩法

一、对象头与Mark Word的解剖:锁的"身份证"

每个Java对象在内存中都有个"身份证"------对象头,其中隐藏着锁的秘密。对象头中的Mark Word(标记字段)记录了锁状态、GC信息、哈希码等。

Mark Word的"七十二变"

  • 无锁状态:存储对象的哈希码和分代年龄。
  • 偏向锁:记录线程ID和偏向时间戳。
  • 轻量级锁:指向栈中锁记录的指针。
  • 重量级锁:指向操作系统互斥量(Mutex)的指针。

举个栗子

当线程A首次访问同步代码时,Mark Word会贴上A的"VIP标签"(偏向锁)。如果线程B来抢锁,VIP标签被撕掉,升级为轻量级锁,此时Mark Word变成线程B的栈锁记录指针。竞争激烈时,最终变成重量级锁,Mark Word指向操作系统级的互斥量。

工具推荐 :用JOL(Java Object Layout)工具打印对象内存布局,直观测锁状态变化。

java 复制代码
// 添加依赖:org.openjdk.jol:jol-core  
Object obj = new Object();  
System.out.println(ClassLayout.parseInstance(obj).toPrintable());  

二、锁升级的"通关秘籍":从偏向锁到重量级锁

锁升级不是"一刀切",而是根据竞争动态调整的智能策略。

  1. 偏向锁延迟 (Biased Locking Delay)

    默认情况下,JVM启动后4秒才会启用偏向锁(通过参数-XX:BiasedLockingStartupDelay=0可关闭延迟)。这是为了避免启动时大量锁竞争导致频繁撤销偏向锁。

  2. 轻量级锁的"自旋赌局"

    线程在轻量级锁状态下会通过自旋(空循环)尝试获取锁。

    • 固定自旋次数:JDK 6之前默认10次。
    • 自适应自旋:JDK 6之后,JVM根据前一次自旋的成功率动态调整次数。
  3. 重量级锁的"终极审判"

    进入重量级锁后,未抢到锁的线程会被挂起,进入阻塞队列等待操作系统调度。此时性能开销最大,但能保证公平性。

锁升级触发条件

  • 偏向锁 → 轻量级锁:有第二个线程尝试获取锁。
  • 轻量级锁 → 重量级锁:自旋失败(比如自旋次数用完,或第三个线程加入竞争)。

三、synchronized的字节码真相:monitorenter和monitorexit

编译后的synchronized代码块会被翻译为两条指令:

  • monitorenter:尝试获取锁。
  • monitorexit:释放锁(无论正常结束还是异常退出)。

反编译看真相

java 复制代码
public void demo() {  
    synchronized (this) {  
        System.out.println("Hello");  
    }  
}  

javap -c反编译后:

yaml 复制代码
public void demo();  
  Code:  
     0: aload_0  
     1: dup  
     2: astore_1  
     3: monitorenter  // 加锁  
     4: getstatic #2  
     7: ldc #3  
     9: invokevirtual #4  
    12: aload_1  
    13: monitorexit   // 正常释放锁  
    14: goto 22  
    17: astore_2  
    18: aload_1  
    19: monitorexit   // 异常时释放锁(finally机制)  
    20: aload_2  
    21: athrow  
    22: return  

注意monitorexit会出现两次,确保锁在异常时也能释放!


四、逃逸分析与锁消除:JVM的"作弊优化"

JVM发现某些锁根本不需要时,会直接"拆掉锁",提升性能。

锁消除(Lock Elision)

java 复制代码
public String concat(String s1, String s2) {  
    StringBuffer sb = new StringBuffer();  
    synchronized(sb) {  // 锁的是局部变量,且未逃逸 → 被JVM优化掉  
        sb.append(s1).append(s2);  
    }  
    return sb.toString();  
}  

由于sb是局部变量且未逃逸出方法,JVM会直接删除同步代码,仿佛从未加锁!

逃逸分析(Escape Analysis)

JVM通过分析对象的作用域,判断是否可以被优化(比如栈上分配、锁消除)。


五、synchronized与ReentrantLock的"全面战争"

特性 synchronized ReentrantLock
锁获取方式 自动获取和释放 必须手动lock()unlock()
可中断性 不支持 支持lockInterruptibly()
公平锁 非公平(默认) 可选公平或非公平
条件变量 只能绑定一个wait/notify队列 支持多个Condition
性能 JDK 6后优化后接近ReentrantLock 高竞争时性能更好

选型建议

  • 简单场景用synchronized(代码简洁,不易出错)。
  • 高竞争、需要超时/中断功能时选ReentrantLock

六、死锁检测与破解:多线程的"密室逃脱"

经典死锁代码

java 复制代码
Object lockA = new Object();  
Object lockB = new Object();  

// 线程1  
new Thread(() -> {  
    synchronized (lockA) {  
        synchronized (lockB) { /* ... */ }  
    }  
}).start();  

// 线程2  
new Thread(() -> {  
    synchronized (lockB) {  
        synchronized (lockA) { /* ... */ }  
    }  
}).start();  

如何破局?

  1. 统一加锁顺序:所有线程按固定顺序获取锁(比如先lockA后lockB)。
  2. 尝试加锁 :用ReentrantLock.tryLock()设置超时时间。
  3. 工具检测 :用jstackVisualVM查看线程dump,定位死锁链。

七、JVM调优参数:锁的"遥控器"

  1. 偏向锁优化

    • -XX:+UseBiasedLocking:启用偏向锁(默认开启)。
    • -XX:BiasedLockingStartupDelay=0:关闭偏向锁延迟。
  2. 自旋锁参数

    • -XX:PreBlockSpin=10:设置自旋次数(JDK 6后已废弃,自适应自旋接管)。
  3. 锁粗化(Lock Coarsening)

    JVM自动合并相邻的同步块,减少锁开销。

    java 复制代码
    // 优化前  
    synchronized (lock) { doA(); }  
    synchronized (lock) { doB(); }  
    
    // 优化后  
    synchronized (lock) { doA(); doB(); }  

八、终极面试题:挑战年薪百万

  1. synchronized能否锁住对象内部的属性修改?

    (答:不能!锁的是对象实例,若直接修改对象属性,需确保所有访问路径都同步。)

  2. 静态同步方法和非静态同步方法是否互斥?

    (答:不互斥!静态方法锁的是Class对象,非静态方法锁的是实例对象。)

  3. synchronized是否可重入?为什么?

    (答:可重入。每次获取锁时,JVM会记录持有锁的线程和进入次数,避免自己锁死自己。)


九、总结:synchronized的"终极奥义"

  • 底层是Monitor:每个对象关联一个监视器,通过操作系统Mutex实现线程调度。
  • 锁升级是核心优化:从偏向锁到重量级锁,兼顾性能和公平。
  • JVM暗藏玄机:逃逸分析、锁消除、锁粗化,默默提升性能。

最后忠告:多线程编程如同走钢丝,synchronized是你的平衡杆------用得好稳如老狗,用不好"秃头"警告!


扩展阅读

(想要成为锁的"驯龙高手"?赶紧动手写代码,用JOLjstack探索锁的微观世界吧!)

相关推荐
MyikJ1 小时前
Java求职面试:从Spring到微服务的技术挑战
java·数据库·spring boot·spring cloud·微服务·orm·面试技巧
MyikJ1 小时前
Java 面试实录:从Spring到微服务的技术探讨
java·spring boot·微服务·kafka·spring security·grafana·prometheus
ShiinaMashirol1 小时前
代码随想录打卡|Day50 图论(拓扑排序精讲 、dijkstra(朴素版)精讲 )
java·图论
cui_hao_nan2 小时前
Nacos实战——动态 IP 黑名单过滤
java
惜.己2 小时前
MySql(十一)
java·javascript·数据库
10000hours2 小时前
【存储基础】NUMA架构
java·开发语言·架构
伍六星2 小时前
动态拼接内容
java·jsp
TeamDev3 小时前
从 SWT Browser 迁移到 JxBrowser
java·前端·eclipse
迢迢星万里灬3 小时前
Java求职者面试指南:DevOps技术栈深度解析
java·ci/cd·docker·kubernetes·jenkins·devops
oioihoii3 小时前
C++23 已移除特性解析
java·jvm·c++23