synchronized 在字节码里是什么、对象头里的锁状态怎么变,都可以直接看到。这篇用 javap 看指令,用 Unsafe 读对象头 mark word,在三个 JDK 版本上对比(锁的竞争与膨胀耗时已有专门的文章)。
0: aload_0
1: getfield #7 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_0
8: dup
9: getfield #13 // Field n:I
12: iconst_1
13: iadd
14: putfield #13 // Field n:I
17: aload_1
18: monitorexit
19: goto 27
22: astore_2
23: aload_1
24: monitorexit
25: aload_2
26: athrow
27: return
异常表:
from to target type
7 19 22 any
22 25 22 any方法:synchronized void method() flags: flags: (0x0020) ACC_SYNCHRONIZED 指令: 0: aload_0 | 1: dup | 2: getfield #13 // Field n:I | 5: iconst_1 | 6: iadd | 7: putfield #13 // Field n:I | 10: return monitorenter 出现次数: 0
刚创建: 0x0000000000000001 低2位=01 无锁 调用 hashCode() 之后: 0x000000610455d601 低2位=01 无锁 synchronized 块内: 0x000000016bd56ac0 低2位=00 轻量级锁(栈上) 离开 synchronized 之后: 0x0000000000000001 低2位=01 无锁 块内调用过 wait(10): 0x0000007c41064c42 低2位=10 重量级(monitor) 离开块之后: 0x0000007c41064c42 低2位=10 重量级(monitor) (JDK 27 同一程序:) 刚创建: 0x0017ac0000000001 低2位=01 无锁 调用 hashCode() 之后: 0x0017ac45e62af801 低2位=01 无锁 synchronized 块内: 0x0017ac0000000000 低2位=00 轻量级锁(栈上) 离开 synchronized 之后: 0x0017ac0000000001 低2位=01 无锁 块内调用过 wait(10): 0x0017aec3226a3002 低2位=10 重量级(monitor) 离开块之后: 0x0017aec3226a3002 低2位=10 重量级(monitor) (JDK 17 同一程序:) 刚创建: 0x0000000000000001 低2位=01 无锁 调用 hashCode() 之后: 0x00000014dad5dc01 低2位=01 无锁 synchronized 块内: 0x000000016b0a2ae0 低2位=00 轻量级锁(栈上) 离开 synchronized 之后: 0x0000000000000001 低2位=01 无锁 块内调用过 wait(10): 0x0000007862d1c542 低2位=10 重量级(monitor) 离开块之后: 0x0000007862d1c542 低2位=10 重量级(monitor)
mark word 的读取用了 sun.misc.Unsafe(编译时加 -XDignore.symbol.file,JDK 27 加 --sun-misc-unsafe-memory-access=allow),这只是为了观察,不要在业务代码里这样做。
块内 holdsLock = true 捕获 boom;块外 holdsLock = false 另一个线程拿到了锁,等待 1695 µs