使用的java版本
src git:(master) ✗ java -version
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
先说结论:
- 对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。
- 对于同步代码块,JVM采用monitorenter、monitorexit两个指令来实现同步。
同步方法
public class MainTest {
public synchronized void test() {
System.out.println("Hello world");
}
}
我们使用javap -v
命令来查看class对应的字节码
javap -v MainTest
MainTest.class对应的字节码的部分代码
public class com.hm.sync_test.MainTest
{
public synchronized void test();
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
LineNumberTable:
line 34: 0
line 35: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/hm/sync_test/MainTest;
}
在上面的代码中我们可以看到test方法有两个flag。
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
ACC_PUBLIC的意思是access public
,标记可见性是public。
ACC_SYNCHRONIZED的意思是access synchronized
,标记是一个synchronized方法。
对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。
方法级的同步是隐式的。同步方法会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个设置了ACC_SYNCHRONIZED标志的方法的时,执行线程需要先获得监视器,然后开始执行方法,方法执行之后再释放监视器。这时如果其他线程来请求执行方法,会因为无法获得监视器而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器会被自动释放。
同步代码块
public class MainTest {
public void test() {
//使用类对象作为锁
synchronized (MainTest.class){
System.out.println("Hello world");
}
}
}
MainTest.class对应的字节码的部分代码
public class com.hm.sync_test.MainTest
{
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // class com/hm/sync_test/MainTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String Hello world
10: invokevirtual #5 // 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采用monitorenter、monitorexit两个指令来实现同步。
同步代码块使用monitorenter和monitorexit两个指令实现。可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。
- 当一个线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果没有获得锁,那么阻塞。
- 当一个线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁,那么锁计数+1(为什么会加1呢,因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况)。当它遇到monitorexit的时候,锁计数器-1,当计数器为0的时候就释放锁。
我们注意到,上面的代码中有2个monitorexit。第14: monitorexit
和第20: monitorexit
行。
这是因为synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是抛出异常,虚拟机释放。图中第2个monitorexit就是发生异常时执行的流程。
而且,从图中我们也可以看到在第15行,有一个goto指令15: goto 23
,也就是说如果正常运行结束会跳转到23行直接返回了。
参考链接:
网友评论