java 同步鎖:synchronized 關鍵字
簡介
synchronized關鍵字是Java裡面最基本的同步手段,它經過編譯之後,會在同步塊的前後分別生成 monitorenter
和 monitorexit
位元組碼指令,這兩個位元組碼指令都需要一個引用型別的引數來指明要鎖定和解鎖的物件;而直接使用 synchronized 關鍵字鎖定方法時,生成的位元組碼指令裡面並沒有 monitorenter 和 monitorexit 這兩個指令,而是為方法添加了一個flags: ACC_SYNCHRONIZED
, 該標識指明瞭該方法是一個同步方法。
synchronized 的兩種不同用法
一、作用於方法上
public synchronized void method1(){ System.out.println(1); }
檢視編譯後的位元組碼,發現會在方法的中加入ACC_SYNCHRONIZED
的標識:
public synchronized void method1();
descriptor: ()V
flags: (0x0021) ACC_PUBLIC, **ACC_SYNCHRONIZED**
Code:
stack=2, locals=1, args_size=1
二、作用於程式碼塊
public void method3(){ synchronized(this){ System.out.println(3); } }
檢視編譯後的位元組碼,發現會在同步塊的前後分別生成 monitorenter
和 monitorexit
位元組碼指令
public void method3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: **monitorenter** 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: iconst_3 8: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 11: aload_1 12: **monitorexit** ... 省略...
實現原理
在學習Java記憶體模型記憶體間的互動操作的時候,知道JAVA記憶體模型還提供了更大範圍的原子性保證 :lock
和 unlock
操作。
但是lock和 unlock操作並 沒有直接提供給使用者使用,而是提供了更高層次的位元組碼指令 monitorenter
和 monitorexit
來隱式的使用lock和 unlock操作。
Lock(鎖定)命令:把一個變數標識為一條執行緒獨佔的狀態,作用於主記憶體的變數
unlock(解鎖)命令:把處於鎖定狀態的變數釋放鎖,縮放鎖後其他執行緒才可以鎖定,作用於主記憶體的變數
而 synchronized 就是使用 monitorenter 和 monitorexit 這兩個指令來實現鎖的功能的。
根據JVM規範的要求,在執行monitorenter指令的時候,首先要去嘗試獲取物件的鎖,如果這個物件沒有被鎖定,或者當前執行緒已經擁有了這個物件的鎖,就把鎖的計數器加1,相應地,在執行monitorexit的時候會把計數器減1,當計數器減小為0時,鎖就釋放了。所以 synchronized 對同一條執行緒來說是一個可重入的。
synchronized 同步塊在已進入的執行緒執行完成之前,會阻塞後面的執行緒進入,而JAVA的執行緒是對映到作業系統的原生執行緒之上的,如果要阻塞或喚醒一個執行緒,都需要作業系統來幫忙完成,這就需要從使用者態切換到核心態中,狀態轉換需要消耗很多的處理器時間,對與簡單的同步塊,狀態轉換消耗的時候可能比使用者程式碼執行的時間還要長,所以 synchronized 是JAVA 語言中一個重量級的操作。
鎖優化
總結
(1)synchronized在編譯時會在同步塊前後生成monitorenter和monitorexit位元組碼指令或者ACC_SYNCHRONIZED的標識;
(2)monitorenter和monitorexit位元組碼指令需要一個引用型別的引數,基本型別不可以哦;
(3)monitorenter和monitorexit位元組碼指令更底層是使用Java記憶體模型的lock和unlock指令;
(4)synchronized是可重入鎖;
(5)synchronized是非公平鎖;
(6)synchronized可以同時保證原子性、可見性、有序性;
(7)synchronized有三種狀態:偏向鎖、輕量級鎖、重量級鎖;