跳转至

synchronized 底层原理(JVM 指令与 monitor 锁)

一、两种用法

// 1. 修饰方法
public synchronized void method() {}

// 2. 修饰代码块
public void block() {
    synchronized (lock) { ... }
}

二、代码块的字节码

public void block() {
    synchronized (this) {
        System.out.println("hi");
    }
}

编译后:

aload_0
dup
astore_1
monitorenter          ; 进入锁
getstatic ...
ldc "hi"
invokevirtual println
aload_1
monitorexit           ; 正常退出
goto goto end
athrow
aload_1
monitorexit           ; 异常退出
end:
return

三、monitor 对象

每个 Java 对象都关联一个 monitor(管程):

  • monitorenter:计数器 +1,为 0 时获取锁。
  • monitorexit:计数器 -1,为 0 时释放。

monitor 存在对象头(Mark Word)里。

四、对象头

对象头分两部分:

|  Mark Word (64 bit)  |  Klass Word (64 bit)  |

Mark Word 存: - hashCode - GC 分代年龄 - 锁状态标志(无锁/偏向/轻量级/重量级) - 锁指针(指向 monitor)

五、锁升级过程

JDK 6 引入锁优化,锁从低到高:

  1. 无锁:刚创建对象。
  2. 偏向锁:第一个线程访问,Mark Word 记线程 ID,以后这个线程进入不用同步。
  3. 轻量级锁:有竞争,CAS 自旋。
  4. 重量级锁:自旋失败,升级为 OS 互斥量,线程阻塞。

锁只能升级不能降级。

六、修饰方法的字节码

方法级 synchronized 不通过 monitorenter/monitorexit,而是方法标志位 ACC_SYNCHRONIZED,JVM 调用方法时检查这个标志,自动加锁。

高频追问

  • synchronized 锁的是对象,不是代码块。
  • 普通方法锁 this,静态方法锁 Class 对象。
  • 偏向锁在 JDK 15 后默认关闭(因为现代应用多线程竞争多,偏向锁收益小)。