1 示例-简单同步代码块
public class SychTest9 {
public static void main(String[] args) {
Object o = new Object();
synchronized (o){
System.out.println("aaa");
}
}
}
使用javap查看指令(复制了部分)
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String aaa
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2
21: monitorexit
22: goto 30
25: astore_3
26: aload_2
27: monitorexit
28: aload_3
29: athrow
30: return
可以看到下面三个指令,一个monitorenter 指令,两个monitorexit
11: monitorenter 进入管程
21: monitorexit 出管程
27: monitorexit 出管程
synchronized
同步语句块的实现使用的是 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置, monitorexit
指令则指明同步代码块的结束位置。**
当执行 monitorenter
指令时,线程试图获取锁也就是获取 对象监视器 monitor
的持有权
在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitor实现的。每个对象中都内置了一个 ObjectMonitor
对象。
另外,wait/notify
等方法也依赖于monitor
对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify
等方法,否则会抛出java.lang.IllegalMonitorStateException
的异常的原因。
在执行monitorenter
时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。
在执行 monitorexit
指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止
这里为什么有两个monitorexit指令呢?
第一个monitorexit指令是同步代码块正常释放锁的一个标志;
如果同步代码块中出现Exception或者Error,则会调用第二个monitorexit指令来保证释放锁
2 示例-简单同步代码块+异常
在同步代码块中手动抛出异常
public class SychTest10 {
public static void main(String[] args) throws Exception {
Object o = new Object();
synchronized (o){
System.out.println("aaa");
throw new Exception();
}
}
}
使用javap查看指令(复制了部分)
/*
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String aaa
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: new #6 // class java/lang/Exception
23: dup
24: invokespecial #7 // Method java/lang/Exception."<init>":()V
27: athrow
28: astore_3
29: aload_2
30: monitorexit
31: aload_3
32: athrow
*/
可以看到monitorenter和monitorexit,athrow和athrow,保证释放锁
11: monitorenter
27: athrow
30: monitorexit
32: athrow
3 示例-普通同步方法
public class SychTest11 {
public synchronized void aa() {
System.out.println("aaa");
}
public static void main(String[] args) {
}
使用javap查看指令(复制了部分)
public synchronized void aa();
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 aaa
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
可以看到,可以没有了monitorenter和monitorexit指令,但是有一个标识ACC_SYNCHRONIZED,标识它是一个同步方法
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
4 示例 静态同步方法
public class SychTest12 {
public static synchronized void aa() {
System.out.println("aaa");
}
public static void main(String[] args) {
aa();
}
}
使用javap查看指令(复制了部分)
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String aaa
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
可以看到,可以没有了monitorenter和monitorexit指令,但是有一个标识ACC_SYNCHRONIZED,标识它是一个同步方法,且ACC_STATIC标识是静态方法
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
5 小结
synchronized
同步语句块的实现使用的是 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。正常情况下回有两个monitorexit
指令,为了在出现异常也能够成功释放锁。
synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。
不过两者的本质都是对监视器 monitor 的获取