Java|Synchronized的基本知識、實現原理以及其與ReentrantLock的區別


synchronized知識

  在談論synchronized之前,我們需要了解線程安全問題的主要誘因。線程安全問題的主要誘因如下:

  • 存在共享數據(也稱為臨界資源)
  • 存在多條線程共同操作這些共享數據

  而解決線程安全的根本方法就是:同一時刻有且只有一個線程在操作共享數據,其他線程必須等到該線程處理完數據后再對共享數據進行操作。

  基於上述,引入了互斥鎖,其具有兩個特性:

  1. 互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程的協調機制,這樣在同一時間只有一個線程對需要同步的代碼塊進行訪問。互斥性也稱作操作的原子性。
  2. 可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨后獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作,從而引起不一致。

  需要注意的是,synchronized鎖的不是代碼,而是對象。

  根據獲取鎖的分類,可分為獲取對象鎖、獲取類鎖。

獲取對象鎖的兩種方法:

1、同步代碼塊(synchronized(this),synchronized(類實例對象)),鎖是小括號中的實例對象。

2、同步非靜態方法(synchronized method),鎖是當前對象的實例對象。

獲取類鎖的兩種方法:

1、同步代碼塊(synchronized(類.class)),鎖是小括號中的類的對象。

2、同步靜態方法(synchronized static method),鎖是當前對象的類對象(class對象)。

接下來,對對象鎖和類鎖進行總結對比:

1、有線程訪問對象的同步代碼塊時,另外的線程可以訪問該對象的非同步代碼塊;

2、若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另一個訪問對象的同步代碼塊的線程會被阻塞;

3、若鎖住的是同一個對象,一個線程在訪問對象的同步方法時,另一個訪問對象的同步方法的線程會被阻塞;

4、若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另一個訪問對象的同步方法的線程會被阻塞,反之亦然;

5、同一類的不同對象的對象鎖互不干擾;

6、類鎖由於也是一種特殊的對象鎖,因此表現和上述的1、2、3、4一致,而由於一個類只有一把對象鎖,所以同一類的不同對象使用類鎖將會是同步的;

7、類鎖和對象鎖互不干擾。

synchronized底層實現原理

  實現synchronized的基礎:Java對象頭+Monitor

  對象頭的結構如下:

虛擬機位數 頭對象結構 說明
32/64bit Mark Word 默認存儲對象的hashcode,分代年齡,鎖類型,鎖標志位等信息。
32/64bit Class Metadata Address 類型指針指向對象的類元數據,JVM通過這個指針確定該對象是哪個類的數據。

  下面是32bit的Mark Word的說明圖:

  

  而對於Monitor,每個Java對象天生自帶一把看不見的鎖(內部鎖/Monitor鎖)。

  Monitor鎖的競爭與釋放如下圖所示:

  

  在早期的Java版本中,synchronized屬於重量級鎖,依賴於Mutex Lock實現。線程之間的切換需要從用戶態轉換到核心態,開銷很大。

  在Java 6之后,synchronized性能得到很大提升。主要是因為引入了:

  1:Adaptive spinning(自適應自旋)

  2:Lock Eliminate(鎖消除)

  3:Lock Coarsening(鎖粗化)

  4:Lightweight Locking(輕量級鎖)

  5:Biased Locking(偏向鎖)

  6:......

  下面對這些概念進行介紹。

  自旋鎖與自適應自旋鎖:

  自旋鎖:在很多情況下,共享數據的鎖定狀態持續時間較短,切換線程不值得。就通過讓線程執行忙循環等待鎖的釋放,而不讓出CPU。缺點是若鎖被其他線程長時間占用,會帶來許多性能上的開銷。用戶使用preBlockspin來修改等待時間、次數,不好設定。

  這樣的話,自適應自旋鎖就出現了,它自旋的次數不固定,由前一次在同一個鎖上的自旋時間及鎖的持有者的狀態來決定。

  鎖消除:

  JIT編譯時,對運行的上下文進行掃描,去除不可能存在競爭的鎖,節省毫無意義的請求鎖的時間。

  鎖粗化:

  通過擴大加鎖的范圍,避免反復加鎖和解鎖。

  偏向鎖:

  減少同一線程獲取鎖的代價。

  大多數情況下,鎖不存在多線程競爭,總是由同一個線程多次獲得。

  核心思想:如果一個線程獲得了鎖,那么鎖就進入了偏向鎖模式,此時Mark Word的結構也變為偏向結構,當該線程再次請求鎖的時候,無需做任何操作,即獲得鎖的過程只需檢查Mark Word的鎖標記位為偏向鎖以及當前線程ID等於Mark Word的ThreadID即可,這樣就省去了大量有關鎖申請的操作。

  不使用與鎖競爭比較激烈的多線程場合。

  輕量級鎖:

  輕量級鎖是由偏向鎖升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖競爭的時候,偏向鎖就會升級為輕量級鎖。

  使用場景:線程交替執行同步塊。

  若存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

synchronized和ReentrantLock的區別

  ReentrantLock稱為重入鎖,位於JUC包的locks,和CountDownLatch、FutureTask一樣基於AQS實現。能夠實現比synchronized更細粒度的控制,比如控制公平性。此外需要注意,調用lock()之后,必須調用unlock()釋放鎖。它的性能未必比synchronized高,並且是可重入的。

  ReentrantLock公平性的設置:

  ReentrantLock fairLock = new ReentrantLock(true);

  參數為true時,傾向於將鎖賦予等待時間最久的線程;

  公平鎖:獲取鎖的順序按先后調用lock方法的順序(慎用);

  非公平鎖:搶占的順序不一定,看運氣;

  synchronized是非公平鎖。

  ReentrantLock能夠將鎖對象化:

  判斷是否有線程,或者某個特定線程,在排隊等候獲取鎖;

  帶超時的獲取鎖的嘗試;

  感知有沒有成功獲取鎖。

  ReentrantLock能夠將wait/notify/notifyAll對象化。

  總結:

  1、synchronized是關鍵字,ReentrantLock是類;

  2、ReentrantLock可以獲取鎖的時間進行設置,避免死鎖;

  3、ReentrantLock可以獲取各種鎖的信息;

  4、ReentrantLock可以靈活實現多路通知;

  5、機制:sync操作Mark Word,lock調用Unsafe類的park()方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM