Java 各類鎖的小結

cool-girl.jpg

一. synchronized

在 JDK 1.6 以前,synchronized 是重量級鎖,效率低下。html

從 JDK 1.6 開始,synchronized 作了不少優化,如偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化等技術來減小鎖操做的開銷。java

synchronized 同步鎖一共包含四種狀態:無鎖、偏向鎖、輕量級鎖、重量級鎖,它會隨着競爭狀況逐漸升級。synchronized 同步鎖能夠升級可是不能夠降級,目的是爲了提升獲取鎖和釋放鎖的效率。git

synchronized 的底層原理

synchronized 修飾的代碼塊

經過反編譯.class文件,經過查看字節碼能夠獲得:在代碼塊中使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令指明同步代碼塊的結束位置。github

synchronized 修飾的方法

一樣查看字節碼能夠獲得:在同步方法中會包含 ACC_SYNCHRONIZED 標記符。該標記符指明瞭該方法是一個同步方法,從而執行相應的同步調用。算法

二. 對象鎖、類鎖、私有鎖

對象鎖:使用 synchronized 修飾非靜態的方法以及 synchronized(this) 同步代碼塊使用的鎖是對象鎖。編程

類鎖:使用 synchronized 修飾靜態的方法以及 synchronized(class) 同步代碼塊使用的鎖是類鎖。併發

私有鎖:在類內部聲明一個私有屬性如private Object lock,在須要加鎖的同步塊使用 synchronized(lock)框架

它們的特性:分佈式

  • 對象鎖具備可重入性。
  • 當一個線程得到了某個對象的對象鎖,則該線程仍然能夠調用其餘任何須要該對象鎖的 synchronized 方法或 synchronized(this) 同步代碼塊。
  • 當一個線程訪問某個對象的一個 synchronized(this) 同步代碼塊時,其餘線程對該對象中全部其它 synchronized(this) 同步代碼塊的訪問將被阻塞,由於訪問的是同一個對象鎖。
  • 每一個類只有一個類鎖,可是類能夠實例化成對象,所以每個對象對應一個對象鎖。
  • 類鎖和對象鎖不會產生競爭。
  • 私有鎖和對象鎖也不會產生競爭。
  • 使用私有鎖能夠減少鎖的細粒度,減小由鎖產生的開銷。

由私有鎖實現的等待/通知機制:工具

Object lock = new Object();

// 由等待方線程實現
synchronized (lock) {
    while (條件不知足) {
       lock.wait();
   }                         
}

// 由通知方線程實現
synchronized (lock) {
   條件發生改變
   lock.notify();                    
}
複製代碼

三. ReentrantLock

ReentrantLock 是一個獨佔/排他鎖。相對於 synchronized,它更加靈活。可是須要本身寫出加鎖和解鎖的過程。它的靈活性在於它擁有不少特性。

ReentrantLock 須要顯示地進行釋放鎖。特別是在程序異常時,synchronized 會自動釋放鎖,而 ReentrantLock 並不會自動釋放鎖,因此必須在 finally 中進行釋放鎖。

它的特性:

  • 公平性:支持公平鎖和非公平鎖。默認使用了非公平鎖。
  • 可重入
  • 可中斷:相對於 synchronized,它是可中斷的鎖,可以對中斷做出響應。
  • 超時機制:超時後不能得到鎖,所以不會形成死鎖。

ReentrantLock 是不少類的基礎,例如 ConcurrentHashMap 內部使用的 Segment 就是繼承 ReentrantLock,CopyOnWriteArrayList 也使用了 ReentrantLock。

四. ReentrantReadWriteLock

我以前寫過一篇文章《ReentrantReadWriteLock讀寫鎖及其在 RxCache 中的使用》 曾詳細介紹過ReentrantReadWriteLock。

它擁有讀鎖(ReadLock)和寫鎖(WriteLock),讀鎖是一個共享鎖,寫鎖是一個排他鎖。

它的特性:

  • 公平性:支持公平鎖和非公平鎖。默認使用了非公平鎖。
  • 可重入:讀線程在獲取讀鎖以後可以再次獲取讀鎖。寫線程在獲取寫鎖以後可以再次獲取寫鎖,同時也能夠獲取讀鎖(鎖降級)。
  • 鎖降級:先獲取寫鎖,再獲取讀鎖,而後再釋放寫鎖的過程。鎖降級是爲了保證數據的可見性。

五. CAS

上面提到的 ReentrantLock、ReentrantReadWriteLock 都是基於 AbstractQueuedSynchronizer (AQS),而 AQS 又是基於 CAS。CAS 的全稱是 Compare And Swap(比較與交換),它是一種無鎖算法。

synchronized、Lock 都採用了悲觀鎖的機制,而 CAS 是一種樂觀鎖的實現。

CAS 的特性:

  • 經過調用 JNI 的代碼實現
  • 非阻塞算法
  • 非獨佔鎖

CAS 存在的問題:

  • ABA
  • 循環時間長開銷大
  • 只能保證一個共享變量的原子操做

六. Condition

Condition 用於替代傳統的 Object 的 wait()、notify() 實現線程間的協做。

在 Condition 對象中,與 wait、notify、notifyAll 方法對應的分別是 await、signal 和 signalAll。

Condition 必需要配合 Lock 一塊兒使用,一個 Condition 的實例必須與一個 Lock 綁定。

它的特性:

  • 一個 Lock 對象能夠建立多個 Condition 實例,因此能夠支持多個等待隊列。
  • Condition 在使用 await、signal 或 signalAll 方法時,必須先得到 Lock 的 lock()
  • 支持響應中斷
  • 支持的定時喚醒功能

七. Semaphore

Semaphore、CountDownLatch、CyclicBarrier 都是併發工具類。

Semaphore 能夠指定多個線程同時訪問某個資源,而 synchronized 和 ReentrantLock 都是一次只容許一個線程訪問某個資源。因爲 Semaphore 適用於限制訪問某些資源的線程數目,所以可使用它來作限流。

Semaphore 並不會實現數據的同步,數據的同步仍是須要使用 synchronized、Lock 等實現。

它的特性:

  • 基於 AQS 的共享模式
  • 公平性:支持公平模式和非公平模式。默認使用了非公平模式。

八. CountDownLatch

CountDownLatch 能夠當作是一個倒計數器,它容許一個或多個線程等待其餘線程完成操做。所以,CountDownLatch 是共享鎖。

CountDownLatch 的 countDown() 方法將計數器減1,await() 方法會阻塞當前線程直到計數器變爲0。

在個人爬蟲框架NetDiscovery中就使用了 CountDownLatch 來控制某個爬蟲的暫停和恢復。

九. 鎖的分類

Java 鎖的小結.png

十. 總結

本文小結了 Java 經常使用的一些鎖及其一些特性,掌握這些鎖是掌握 Java 併發編程的基礎。固然,Java 的鎖並不止這些,例如 ConcurrentHashMap 的分段鎖(Segment),分佈式環境下所使用的分佈式鎖。

參考資料:

  1. 《Java併發編程藝術》
  2. 《Java併發編程實戰》
  3. 不可不說的Java「鎖」事

Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。

相關文章
相關標籤/搜索