并发 - synchronized

synchronized

锁的对象是谁

当你使用 synchronized 关键字的时候,JVM 底层使用 Monitor 锁来实现同步。而锁的对象可以分为:

  • 如果 synchronized 的是普通方法,那么锁是当前实例
  • 如果 synchronized 的是静态方法,那么锁是当前类的 Class
  • 如果 synchronized 的是同步块,那么锁是括号里面的对象

synchronized 同步块

底层基于 monitorentermonitorexit 这一对指令实现的。

public void foo(Object lock) {
    synchronized (lock) {
        lock.hashCode();
    }
}

上面的Java代码将编译为下面的字节码:

public void foo(java.lang.Object);
Code:
    0: aload_1
    1: dup
    2: astore_2
    3: monitorenter
    4: aload_1
    5: invokevirtual java/lang/Object.hashCode:()I
    8: pop
    9: aload_2
    10: monitorexit
    11: goto          19
    14: astore_3
    15: aload_2
    16: monitorexit
    17: aload_3
    18: athrow
    19: return
Exception table:
    from    to  target type
        4    11    14   any
        14    17    14   any

synchronized 方法

方法标记为 ACC_SYNCHRONIZED,同样需要进行 monitorenter 操作。

public synchronized void foo(Object lock) {
    lock.hashCode();
}

上面的 Java 代码将编译为下面的字节码:

public synchronized void foo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
    stack=1, locals=2, args_size=2
        0: aload_1
        1: invokevirtual java/lang/Object.hashCode:()I
        4: pop
        5: return

synzhronized 性能改进

在 Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作

现代的(Oracle)JDK 中,JVM 对此进行了非常大地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)轻量级锁重量级锁,大大改进了其性能。

所谓锁的升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。

对象头

对于 HotSpot JVM,Java 对象保存在内存中时,由对象头、实例数据、对齐填充字节组成。对象头由 **Mark Word、指向类的指针、数组长度(只有数组对象才有)**组成。

Mark Word 记录了对象和锁有关的信息,当这个对象被 synchronized 关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word 有关。Mark Word 在 32 位 JVM 中的长度是 32 bit,在 64 位 JVM 中长度是 64 bit。Mark Word 在不同的锁状态下存储的内容不同,在 32 位JVM中是这么存的:

偏斜锁

当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。

轻量级锁

如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。

重量级锁

轻量级锁 CAS 获取锁失败,会升级为重量级锁。

锁降级

当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。HotSpot JVM 的 Stop-the-World 机制称为 safepoint,在此期间,所有线程(不含 JNI 线程)会被挂起。

可重入与公平性

synchronized 是非公平锁,可以重入。