Java同步處理底層實現(monitor 可重入鎖)
對於Java同步處理可以參考這篇部落格:https://blog.csdn.net/sophia__yu/article/details/83990755
但是這些處理方法的底層實現是怎樣呢?
接下里將會解釋同步程式碼塊、同步方法、全域性鎖的底層實現。
同步程式碼塊底層實現:
首先看一個簡單的同步程式碼塊:
/////同步程式碼塊底層實現
public class DiCeng {
public static void main(String[] args)
{
Object obj=new Object();
synchronized ( obj) //把obj物件鎖住
{
System.out.println("hello sun");
}
}
}
先將Java程式編譯:javac -encoding UTF-8 DiCeng.java ;
然後進行反編譯:javap -c DiCeng ;
下面程式碼是反編譯後底層程式碼:
public static void main(java.lang.String[]);
Code:
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 ////注意:monitorenter進入同步程式碼塊
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String hello sun
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2
21: monitorexit /////注意:monitorexit正常退出同步程式碼塊進行解鎖
22: goto 30
25: astore_3
26: aload_2
27: monitorexit /////注意:monitorexit異常退出同步塊進行解鎖
28: aload_3
29: athrow
30: return
Exception table:
from to target type
12 22 25 any
25 28 25 any
在反編譯後代碼後,我們可以看見1個monitorenter,2個monitorexit ,這些代表什麼意思呢?解釋如下:
在進入同步程式碼塊時,首先需要執行monitorenter,退出時執行monitorexit進行解鎖。
使用synchronized實現同步,關鍵點在於獲取物件的監視器monitor物件,當獲取到monitor物件後,才可以執行同步程式碼塊即執行monitorenter操作,否則,就沒有monitorenter操作並且等待。獲取monitor物件是互斥操作,即同一時刻只能有一個執行緒可以獲取到要鎖物件的monitor監視器。
有1個monitorenter,2個monitorexit是因為:通常一個monitor指令會包含多個monitorexit指令。是因為JVM虛擬機器需要確保所獲取的鎖不論是正常執行路徑或者異常執行路徑都能正確解鎖,
否則當發生異常突然退出時沒有解鎖操作,那麼其他執行緒也獲取不到這個物件的鎖,其他執行緒就無法正確執行。
同步方法底層實現:
////同步方法
public class DiCeng {
public static void main(String[] args)
{
new DiCeng().print();
}
synchronized public void print()
{
System.out.println("hello sun");
}
}
先將Java程式編譯:javac -encoding UTF-8 DiCeng.java ;
然後進行反編譯:javap -v DiCeng ;(注意:方法是javap -v )
反編譯後代碼如下:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: new #2 // class CODE/多執行緒/DiCeng
3: dup
4: invokespecial #3 // Method "<init>":()V
7: invokevirtual #4 // Method print:()V
10: return
LineNumberTable:
line 49: 0
line 50: 10
public synchronized void print();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED //ACC_SYNCHRONIZED標識
Code:
stack=2, locals=1, args_size=1
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String hello sun
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 53: 0
line 54: 8
當使用synchronized標記方法時,位元組碼會出現訪問標記ACC_SYNCHRONIZED。該標記表示:在進入該方法時,JVM需要進行 monitorenter操作,獲取物件的監視器monitor;在退出該方法時無論是正常返回,JVM均需要進行monitorexit操作。
不論是同步程式碼塊還是同步方法,都要執行monitorenter操作獲取該物件的monitor監視器,如果物件的monitor的計數器為0,表示該物件沒有被其他執行緒所持有,此時JVM會將該 鎖物件的持有執行緒設定為當前執行緒,並且將monitor計數器加1。
在目標鎖物件的計數器不為0的情況下,如果鎖物件的持有執行緒是當前執行緒,JVM可以將計數器再次加1(可重入鎖),否則需要等待,直到持有執行緒釋放該鎖。
可重入鎖:
可重入鎖指當前執行緒獲取到該物件的鎖後,該物件的所有同步方法這個執行緒都可以訪問。因為該物件的鎖已經被這個執行緒獲取到,那麼物件裡面的東西這個執行緒都可以用,就比如說我用鎖開啟門後,門裡的東西我都能用,門外的人用不了門裡面的東西。
//可重入鎖
class Mythread4 implements Runnable
{
public void run()
{
print1();
print2();
}
///執行緒1先獲取到Mythread4例項化物件的鎖,monitor監視器變為1
public synchronized void print1()
{
if(Thread.currentThread().getName().equals("執行緒1"));
{
print2(); //進入同步方法print2,monitor不為0,但是鎖持有的執行緒是當前執行緒1,將mobitor變為2
while(true) {
// 一個迴圈使執行緒1一直不釋放鎖,那麼執行緒2就無法獲取鎖
}
}
}
////執行緒2啟動後,該方法是同步方法,但是執行緒1沒有釋放鎖,monitor不為0,所以執行緒2進不了該方法
public synchronized void print2()
{
if(Thread.currentThread().getName().equals("執行緒1"))
{
System.out.println("執行緒1進入另一個同步方法");
}
else
{
System.out.println("執行緒1沒有釋放鎖,執行緒2進入同步方法");
}
}
}
public class DiCeng
{
public static void main(String[] args) throws InterruptedException {
Mythread4 thread=new Mythread4();
new Thread(thread,"執行緒1").start();
Thread.sleep(5000); //確保執行緒1先獲取到鎖
new Thread(thread,"執行緒2").start();
}
}
執行緒1可以進入其他同步方法,然後一直在迴圈裡面,執行緒2無法獲取到鎖。