深刻介紹Java中的鎖[原理、鎖優化、CAS、AQS]

一、爲何要用鎖?

鎖-是爲了解決併發操做引發的髒讀、數據不一致的問題。java

二、鎖實現的基本原理

2.一、volatile編程

Java編程語言容許線程訪問共享變量, 爲了確保共享變量能被準確和一致地更新,線程應該確保經過排他鎖單獨得到這個變量。Java語言提供了volatile,在某些狀況下比鎖要更加方便。安全

volatile在多處理器開發中保證了共享變量的「 可見性」。可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。數據結構

Java中的鎖[原理、鎖優化、CAS、AQS]

結論:若是volatile變量修飾符使用恰當的話,它比synchronized的使用和執行成本更低,由於它不會引發線程上下文的切換和調度。併發

2.二、synchronized框架

synchronized經過鎖機制實現同步。編程語言

先來看下利用synchronized實現同步的基礎:Java中的每個對象均可以做爲鎖。性能

具體表現爲如下3種形式。優化

  • 對於普通同步方法,鎖是當前實例對象。this

  • 對於靜態同步方法,鎖是當前類的Class對象。

  • 對於同步方法塊,鎖是Synchonized括號裏配置的對象。

當一個線程試圖訪問同步代碼塊時,它首先必須獲得鎖,退出或拋出異常時必須釋放鎖。

2.2.1 synchronized實現原理

synchronized是基於Monitor來實現同步的。

Monitor從兩個方面來支持線程之間的同步:

  • 互斥執行

  • 協做

一、Java 使用對象鎖 ( 使用 synchronized 得到對象鎖 ) 保證工做在共享的數據集上的線程互斥執行。

二、使用 notify/notifyAll/wait 方法來協同不一樣線程之間的工做。

三、Class和Object都關聯了一個Monitor。

Java中的鎖[原理、鎖優化、CAS、AQS]

Monitor 的工做機理

  • 線程進入同步方法中。

  • 爲了繼續執行臨界區代碼,線程必須獲取 Monitor 鎖。若是獲取鎖成功,將成爲該監視者對象的擁有者。任一時刻內,監視者對象只屬於一個活動線程(The Owner)

  • 擁有監視者對象的線程能夠調用 wait() 進入等待集合(Wait Set),同時釋放監視鎖,進入等待狀態。

  • 其餘線程調用 notify() / notifyAll() 接口喚醒等待集合中的線程,這些等待的線程須要從新獲取監視鎖後才能執行 wait() 以後的代碼。

  • 同步方法執行完畢了,線程退出臨界區,並釋放監視鎖。

參考文檔:https://www.ibm.com/developerworks/cn/java/j-lo-synchronized

2.2.2 synchronized具體實現

一、同步代碼塊採用monitorenter、monitorexit指令顯式的實現。

二、同步方法則使用ACC_SYNCHRONIZED標記符隱式的實現。

經過實例來看看具體實現:

public class SynchronizedTest { public synchronized void method1(){ System.out.println("Hello World!"); } public void method2(){ synchronized (this){ System.out.println("Hello World!"); } }}

javap編譯後的字節碼以下:

Java中的鎖[原理、鎖優化、CAS、AQS]

 

monitorenter

每個對象都有一個monitor,一個monitor只能被一個線程擁有。當一個線程執行到monitorenter指令時會嘗試獲取相應對象的monitor,獲取規則以下:

  • 若是monitor的進入數爲0,則該線程能夠進入monitor,並將monitor進入數設置爲1,該線程即爲monitor的擁有者。

  • 若是當前線程已經擁有該monitor,只是從新進入,則進入monitor的進入數加1,因此synchronized關鍵字實現的鎖是可重入的鎖。

  • 若是monitor已被其餘線程擁有,則當前線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor。

monitorexit

只有擁有相應對象的monitor的線程才能執行monitorexit指令。每執行一次該指令monitor進入數減1,當進入數爲0時當前線程釋放monitor,此時其餘阻塞的線程將能夠嘗試獲取該monitor。

2.2.3 鎖存放的位置

鎖標記存放在Java對象頭的Mark Word中。

Java中的鎖[原理、鎖優化、CAS、AQS]

Java對象頭長度

Java中的鎖[原理、鎖優化、CAS、AQS]

32位JVM Mark Word 結構

Java中的鎖[原理、鎖優化、CAS、AQS]

32位JVM Mark Word 狀態變化

Java中的鎖[原理、鎖優化、CAS、AQS]

64位JVM Mark Word 結構

2.2.3 synchronized的鎖優化

JavaSE1.6爲了減小得到鎖和釋放鎖帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」。

在JavaSE1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級。

鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提升得到鎖和釋放鎖的效率。

偏向鎖:

無鎖競爭的狀況下爲了減小鎖競爭的資源開銷,引入偏向鎖。

Java中的鎖[原理、鎖優化、CAS、AQS]

 

輕量級鎖:

輕量級鎖所適應的場景是線程交替執行同步塊的狀況。

Java中的鎖[原理、鎖優化、CAS、AQS]

 

鎖粗化(Lock Coarsening):也就是減小沒必要要的緊連在一塊兒的unlock,lock操做,將多個連續的鎖擴展成一個範圍更大的鎖。

鎖消除(Lock Elimination):鎖削除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行削除。

適應性自旋(Adaptive Spinning):自適應意味着自旋的時間再也不固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。若是在同一個鎖對象上,自旋等待剛剛成功得到過鎖,而且持有鎖的線程正在運行中,那麼虛擬機就會認爲此次自旋也頗有可能再次成功,進而它將容許自旋等待持續相對更長的時間,好比100個循環。另外一方面,若是對於某個鎖,自旋不多成功得到過,那在之後要獲取這個鎖時將可能省略掉自旋過程,以免浪費處理器資源。

2.2.4 鎖的優缺點對比

Java中的鎖[原理、鎖優化、CAS、AQS]

 

2.三、CAS

CAS,在Java併發應用中一般指CompareAndSwap或CompareAndSet,即比較並交換。

一、CAS是一個原子操做,它比較一個內存位置的值而且只有相等時修改這個內存位置的值爲新的值,保證了新的值老是基於最新的信息計算的,若是有其餘線程在這期間修改了這個值則CAS失敗。CAS返回是否成功或者內存位置原來的值用於判斷是否CAS成功。

二、JVM中的CAS操做是利用了處理器提供的CMPXCHG指令實現的。

優勢:

  • 競爭不大的時候系統開銷小。

缺點:

【1】循環時間長開銷大。CAS長時間自旋不成功,給CPU帶來很大的性能開銷。解決方法:JVM能支持pause指令,效率會有必定的提高。
【2】只能保證一個共享變量的原子操做。對多個共享變量操做時,不能保證原子性。 解決方法:加鎖;共享變量合併成一個共享變量
【3】ABA的問題。解決方法就是:增長版本號,每次使用的時候版本號+1,每次變量更新的時候版本號+1。java提供AtomicStampzedReference來解決ABA問題。

三、Java中的鎖實現

3.一、隊列同步器(AQS)

隊列同步器AbstractQueuedSynchronizer(如下簡稱同步器),是用來構建鎖或者其餘同步組件的基礎框架。

3.1.一、它使用了一個int成員變量表示同步狀態。

Java中的鎖[原理、鎖優化、CAS、AQS]

 

3.1.二、經過內置的FIFO雙向隊列來完成獲取鎖線程的排隊工做。

  • 同步器包含兩個節點類型的應用,一個指向頭節點,一個指向尾節點,未獲取到鎖的線程會建立節點線程安全(compareAndSetTail)的加入隊列尾部。同步隊列遵循FIFO,首節點是獲取同步狀態成功的節點。

    Java中的鎖[原理、鎖優化、CAS、AQS]

     

  • 未獲取到鎖的線程將建立一個節點,設置到尾節點。以下圖所示:

Java中的鎖[原理、鎖優化、CAS、AQS]

 

  • 首節點的線程在釋放鎖時,將會喚醒後繼節點。然後繼節點將會在獲取鎖成功時將本身設置爲首節點。以下圖所示:

    Java中的鎖[原理、鎖優化、CAS、AQS]

     

3.1.三、獨佔式/共享式鎖獲取

獨佔式:有且只有一個線程能獲取到鎖,如:ReentrantLock。</pre>

共享式:能夠多個線程同時獲取到鎖,如:CountDownLatch

獨佔式

  • 每一個節點自旋觀察本身的前一節點是否是Header節點,若是是,就去嘗試獲取鎖。

    Java中的鎖[原理、鎖優化、CAS、AQS]

     

  • 獨佔式鎖獲取流程:

Java中的鎖[原理、鎖優化、CAS、AQS]

共享式:

  • 共享式與獨佔式的區別:

    Java中的鎖[原理、鎖優化、CAS、AQS]

  • 共享鎖獲取流程:

Java中的鎖[原理、鎖優化、CAS、AQS]

四、鎖的使用用例

4.一、ConcurrentHashMap的實現原理及使用

Java中的鎖[原理、鎖優化、CAS、AQS]

ConcurrentHashMap類圖

Java中的鎖[原理、鎖優化、CAS、AQS]

ConcurrentHashMap數據結構

結論:ConcurrentHashMap使用的鎖分段技術。首先將數據分紅一段一段地存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問。

相關文章
相關標籤/搜索