实现原理
实现原理
Monitor
synchronized 关键字依赖于内部的 intrinsic lock 或者所谓的 monitor lock。每个对象都有一个与之关联的固有锁。按照惯例,线程必须在访问对象之前获取对象的监视器锁,然后在完成对它们的锁定后释放该监视器锁。据说线程在获得锁和释放锁之间拥有该锁。只要一个线程拥有监视器锁,其他任何线程都无法获得相同的锁。另一个线程在尝试获取锁时将阻塞。当线程释放锁时,将在该动作与任何随后的相同锁获取之间建立 happens-before 关系。
public class SynchronizedDemo {
// 同步方法
public synchronized void syncMethod() {
System.out.println("Hello World");
}
// 同步代码块
public void syncBlock() {
synchronized (this) {
System.out.println("Hello World");
}
}
}
以上的示例代码,编译后的字节码为:
public synchronized void syncMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public void syncBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #5 // class com/hollis/SynchronizedTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #3 // String Hello World
10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
从 JVM 规范中可以看到 synchronized 在 JVM 里的实现原理,JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用 monitorenter 和 monitorexit 指令实现的,而方法同步是使用另外一种方式实现的,细节在 JVM 规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。
monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处,JVM 要保证每个 monitorenter 必须有对应的 monitorexit 与之配对。任何对象都有一个 monitor 与之关联,当且一个 monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。
Java 对象头
synchronized 用的锁是存在 Java 对象头里的。如果对象是数组类型,则虚拟机用 3 个字宽(Word)存储对象头,如果对象是非数组类型,则用 2 字宽存储对象头。在 32 位虚拟机中,1 字宽等于 4 字节,即 32bit,如下图所示:
Java 对象头里的 Mark Word 里默认存储对象的 HashCode、分代年龄和锁标记位。32 位 JVM 的 Mark Word 的默认存储结构如下表所示。
在运行期间,Mark Word 里存储的数据会随着锁标志位的变化而变化。Mark Word 可能变化为存储以下 4 种数据:
在 64 位虚拟机下,Mark Word 是 64bit 大小的,其存储结构如下所示: