synchronized既保證原子性,又保證內存可見性,是一種線程同步的方式,是鎖機制的一種java實現。synchronized的實現基於JVM底層,JVM是基於monitor實現的,而monitor的實現依賴於操作系統的互斥實現。
synchronized語義是同步,但同步有兩層含義:
互斥保證在線程退出前,所有對象狀態變更都對其他線程不可見;可見保證在線程進入同步代碼塊時,可以看到上一個線程對對象狀態變更的最終狀態。
線程安全表明在多線程環境中,不會有多個線程同時訪問共享數據。
線程同步是線程訪問類和實例字段變量,和其他共享資源的一種串行化行為,確保在同一時間只能有一個線程訪問資源。舉個栗子,春運火車票只剩下最後一張火車票,A,B都要搶這張火車票,怎麼解決這個問題防止超賣呢?把資源保護起來,讓A,B排隊來買火車票。
線程安全是屬性,線程同步是方式。
synchronized同步代碼塊是通過monitorenter和monitorexit指令實現的,而synchronized同步方法是基於ACC_SYCHRONIZED標志,同步方法被調用時JVM會檢查這個標志。monitorenter標記臨界區的開始,線程執行到 monitorenter 指令時,將會嘗試獲取對象所對應的 monitor 的所有權;monitorexit標記臨界區的結束,線程執行到 monitorexit 指令時,將釋放對象所對應的 monitor 的所有權。
1 public class SynchronizedMethod { 2 public synchronized void methodA() { 3 System.out.println("MethodA start"); 4 5 } 6 }
將這段代碼通過 javap -c
反編譯一下,重點關注一下編譯後的第3行和第13行。
1 Compiled from "SynchronizedTest.java" 2 public class com.memory.SynchronizedTest { 3 public com.memory.SynchronizedTest(); 4 Code: 5 0: aload_0 6 1: invokespecial #1 // Method java/lang/Object."<init>":()V 7 4: return 8 9 public void methodA(); 10 Code: 11 0: aload_0 12 1: dup 13 2: astore_1 14 3: monitorenter 15 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 16 7: ldc #3 // String MethodA start 17 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 18 12: aload_1 19 13: monitorexit 20 14: goto 22 21 17: astore_2 22 18: aload_1 23 19: monitorexit 24 20: aload_2 25 21: athrow 26 22: return 27 Exception table: 28 from to target type 29 4 14 17 any 30 17 20 17 any 31 }
鎖的種類
java synchronized鎖升級
JDK1.6中對synchronized優化引入了偏向鎖,輕量級鎖,重量級鎖。鎖的升級過程是單方向的,只允許從低到高升級,不允許降級。
重量級鎖(Heavyweight Lock)是將程序運行交出控制權,將線程掛起,由操作系統來負責線程間的調度,負責線程的阻塞和執行。這樣會出現頻繁地對線程運行狀態的切換,線程的掛起和喚醒,消耗大量的系統資源,導致性能低下。
輕量級鎖(lightweight Locking)是相對於重量級鎖而言的,在synchronized實現中使用自旋的方式,實際是通過CPU自旋等待的方式替代線程切換,競爭的線程不會因此而阻塞,避免阻塞喚醒造成的CPU負荷。采用自旋的方式有利有弊,當鎖占用的時間較短時,較少次數的自旋等待就可以獲取鎖;但在鎖占用的時間較長時,自旋會白白浪費大量的CPU資源。因此自旋的次數有一定要在限定之內,自旋失敗就會立即將鎖升級為重量級鎖,稱為鎖膨脹。
偏向鎖(Biased Locking )從字面含義是這把鎖是有私心的,會傾向於上次訪問的線程。Hotspot的作者在他的論文《QRL-OpLocks-BiasedLocking》中闡述到,研究發現大多數情況下不存在多線程爭奪共享資源,而且總是由同一線程多次獲得,考慮到CAS (Compare-And-Swap)指令在獲取Java監視器時會造成較大的CPU延遲,為了讓線程獲得鎖的代價更低而引入了偏向鎖。
64位虛擬機中,標記字段(Mark Word)中包含哈希嗎(HashCode,存放31bits對象的hashcode值),GC分代年齡(Generational GC Age,4bits,因此分代年齡最高為15),偏向線程ID,偏向鎖標記。
synchronized鎖的四個狀態:無鎖狀態,偏向鎖,輕量級鎖和重量級鎖,在Mark Word中對應不同的字段。
java synchronized不同級別鎖中的Mark Word
我是葛一凡,希望對你有用。