上一篇:《Java并发系列(13)——线程池的选择与参数设置》
synchronized 实现已经不属于 Java 层面了,它是 JVM 的范畴,而且不是 JVM 规范而是 JVM 实现的范畴,比如:HotSpot,JRocket,J9 等。
所以要研究 synchronized 实现,有能力最好是研读 JVM 源码。
有一个 monitorenter 至少有一个 monitorexit,通常会有多个。
Java 代码:
package per.lvjc.concurrent.synchro;
public class SynchroTest {
public static void main(String[] args) {
synchronized (SynchroTest.class) {
System.out.println();
synchronized (SynchroTest.class) {
try {
System.out.println();
} catch (Exception e) {
System.out.println();
}
}
}
}
}
javap:
javap -v -l -c -p C:\Users\lvjc\IdeaProjects\concurrent\target\classes\per\lvjc\concurrent\synchro\SynchroTest.class > D:
\data\jvm\SynchroTest_javap.txt
输出的 SynchroTest_javap.txt 文件,节选其中 main 方法:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=6, args_size=1
0: ldc #2 // class per/lvjc/concurrent/synchro/SynchroTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: invokevirtual #4 // Method java/io/PrintStream.println:()V
11: ldc #2 // class per/lvjc/concurrent/synchro/SynchroTest
13: dup
14: astore_2
15: monitorenter
16: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
19: invokevirtual #4 // Method java/io/PrintStream.println:()V
22: goto 32
25: astore_3
26: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
29: invokevirtual #4 // Method java/io/PrintStream.println:()V
32: aload_2
33: monitorexit
34: goto 44
37: astore 4
39: aload_2
40: monitorexit
41: aload 4
43: athrow
44: aload_1
45: monitorexit
46: goto 56
49: astore 5
51: aload_1
52: monitorexit
53: aload 5
55: athrow
56: return
Exception table:
from to target type
16 22 25 Class java/lang/Exception
16 34 37 any
37 41 37 any
5 46 49 any
49 53 49 any
这里面就有 2 个 monitorenter 和 4 个 monitorexit。
下面列举 4 种场景,把这 4 个 monitorexit 都用到:
进一步,monitorenter 和 monitorexit 指令是怎么实现的,那就要深入到 JVM 源码。
虽然 JVM 是 JDK 的一部分,但 Java 开发所使用的 JDK 是经过编译的,里面已经看不到 JVM 源码了。
OpenJDK 是开源的,可以在官网下载到未编译的源码:OpenJDK8 源码下载。
下载到源码之后,可以在 bytecodeInterpreter.cpp 文件里面看到所有 JVM 指令的实现入口:
图上左边可以看到所有的 JVM 指令,每个 JVM 指令用一个字节表示,所以 JVM 指令最多 256 个。
搜索 monitorenter 指令,就可以找到它的实现:
此处暂时不深入 JVM 源码,仅提供一种研究思路,有能力有时间的读者可自行研究源码。
synchronized 实现其实很容易猜出来,与 Java 实现的 AQS 原理几乎是一样的。
根据 AQS 的实现思路,要实现一个锁,只需要存储三个信息即可:
队列的实现很简单,锁状态的存储与计算是一件比较麻烦的事情,而且会随着锁的特性变多而变得更复杂,比如:
而 synchronized 是怎么存储锁状态的呢?答案是对象头。
后面可以看到 synchronized 加锁与解锁其实就是在玩转对象头,这也是为什么说 synchronized 是对象锁,它锁的一定是对象,因为没有对象就没有对象头,以当前 synchronized 实现来说是加不了锁的。
一个 Java 对象里面包含的数据主要有三部分:
对象头又分为两个部分(也可以说是三部分):
mark word 比较复杂,它可能会存储下面这些数据:
为什么说“可能”呢,因为 mark word 空间有限,这里面有些数据是互斥的,“有你没我”。
关于 mark word 暂时只提这么多,后面会结合具体的场景来详细说明。
后面会经常把锁对象的对象头打印出来查看,所以这里先讲一下打印对象头的方法。
引入 OpenJDK 提供的一个 jol 包:
<dependency>
<groupId>org.openjdk.jolgroupId>
<artifactId>jol-coreartifactId>
<version>0.14version>
dependency>
非数组对象的对象头:
package per.lvjc.concurrent.synchro;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j(topic = "synchro")
public class CommonObjectHeader {
private static int i = 1;
private boolean bo = false;
private long lo = 6;
private Object o = new Object();
public static void main(String[] args) {
log.debug(ClassLayout.parseInstance(new CommonObjectHeader()).toPrintable());
}
}
运行结果(64 位系统,默认开启指针压缩):
22:30:38.243 [main] DEBUG synchro - per.lvjc.concurrent.synchro.CommonObjectHeader object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 1 boolean CommonObjectHeader.bo false
13 3 (alignment/padding gap)
16 8 long CommonObjectHeader.lo 6