Java 各種鎖的小結(jié)

cool-girl.jpg

一. synchronized

在 JDK 1.6 之前,synchronized 是重量級(jí)鎖,效率低下。

從 JDK 1.6 開始,synchronized 做了很多優(yōu)化,如偏向鎖、輕量級(jí)鎖、自旋鎖、適應(yīng)性自旋鎖、鎖消除、鎖粗化等技術(shù)來(lái)減少鎖操作的開銷。

synchronized 同步鎖一共包含四種狀態(tài):無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖,它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。synchronized 同步鎖可以升級(jí)但是不可以降級(jí),目的是為了提高獲取鎖和釋放鎖的效率。

synchronized 的底層原理

synchronized 修飾的代碼塊

通過(guò)反編譯.class文件,通過(guò)查看字節(jié)碼可以得到:在代碼塊中使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令指明同步代碼塊的結(jié)束位置。

synchronized 修飾的方法

同樣查看字節(jié)碼可以得到:在同步方法中會(huì)包含 ACC_SYNCHRONIZED 標(biāo)記符。該標(biāo)記符指明了該方法是一個(gè)同步方法,從而執(zhí)行相應(yīng)的同步調(diào)用。

二. 對(duì)象鎖、類鎖、私有鎖

對(duì)象鎖:使用 synchronized 修飾非靜態(tài)的方法以及 synchronized(this) 同步代碼塊使用的鎖是對(duì)象鎖。

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

私有鎖:在類內(nèi)部聲明一個(gè)私有屬性如private Object lock,在需要加鎖的同步塊使用 synchronized(lock)

它們的特性:

  • 對(duì)象鎖具有可重入性。
  • 當(dāng)一個(gè)線程獲得了某個(gè)對(duì)象的對(duì)象鎖,則該線程仍然可以調(diào)用其他任何需要該對(duì)象鎖的 synchronized 方法或 synchronized(this) 同步代碼塊。
  • 當(dāng)一個(gè)線程訪問(wèn)某個(gè)對(duì)象的一個(gè) synchronized(this) 同步代碼塊時(shí),其他線程對(duì)該對(duì)象中所有其它 synchronized(this) 同步代碼塊的訪問(wèn)將被阻塞,因?yàn)樵L問(wèn)的是同一個(gè)對(duì)象鎖。
  • 每個(gè)類只有一個(gè)類鎖,但是類可以實(shí)例化成對(duì)象,因此每一個(gè)對(duì)象對(duì)應(yīng)一個(gè)對(duì)象鎖。
  • 類鎖和對(duì)象鎖不會(huì)產(chǎn)生競(jìng)爭(zhēng)。
  • 私有鎖和對(duì)象鎖也不會(huì)產(chǎn)生競(jìng)爭(zhēng)。
  • 使用私有鎖可以減小鎖的細(xì)粒度,減少由鎖產(chǎn)生的開銷。

由私有鎖實(shí)現(xiàn)的等待/通知機(jī)制:

Object lock = new Object();

// 由等待方線程實(shí)現(xiàn)
synchronized (lock) {
    while (條件不滿足) {
       lock.wait();
   }                         
}

// 由通知方線程實(shí)現(xiàn)
synchronized (lock) {
   條件發(fā)生改變
   lock.notify();                    
}

三. ReentrantLock

ReentrantLock 是一個(gè)獨(dú)占/排他鎖。相對(duì)于 synchronized,它更加靈活。但是需要自己寫出加鎖和解鎖的過(guò)程。它的靈活性在于它擁有很多特性。

ReentrantLock 需要顯示地進(jìn)行釋放鎖。特別是在程序異常時(shí),synchronized 會(huì)自動(dòng)釋放鎖,而 ReentrantLock 并不會(huì)自動(dòng)釋放鎖,所以必須在 finally 中進(jìn)行釋放鎖。

它的特性:

  • 公平性:支持公平鎖和非公平鎖。默認(rèn)使用了非公平鎖。
  • 可重入
  • 可中斷:相對(duì)于 synchronized,它是可中斷的鎖,能夠?qū)χ袛嘧鞒鲰憫?yīng)。
  • 超時(shí)機(jī)制:超時(shí)后不能獲得鎖,因此不會(huì)造成死鎖。

ReentrantLock 是很多類的基礎(chǔ),例如 ConcurrentHashMap 內(nèi)部使用的 Segment 就是繼承 ReentrantLock,CopyOnWriteArrayList 也使用了 ReentrantLock。

四. ReentrantReadWriteLock

我之前寫過(guò)一篇文章《ReentrantReadWriteLock讀寫鎖及其在 RxCache 中的使用》 曾詳細(xì)介紹過(guò)ReentrantReadWriteLock。

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

它的特性:

  • 公平性:支持公平鎖和非公平鎖。默認(rèn)使用了非公平鎖。
  • 可重入:讀線程在獲取讀鎖之后能夠再次獲取讀鎖。寫線程在獲取寫鎖之后能夠再次獲取寫鎖,同時(shí)也可以獲取讀鎖(鎖降級(jí))。
  • 鎖降級(jí):先獲取寫鎖,再獲取讀鎖,然后再釋放寫鎖的過(guò)程。鎖降級(jí)是為了保證數(shù)據(jù)的可見性。

五. CAS

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

synchronized、Lock 都采用了悲觀鎖的機(jī)制,而 CAS 是一種樂(lè)觀鎖的實(shí)現(xiàn)。

CAS 的特性:

  • 通過(guò)調(diào)用 JNI 的代碼實(shí)現(xiàn)
  • 非阻塞算法
  • 非獨(dú)占鎖

CAS 存在的問(wèn)題:

  • ABA
  • 循環(huán)時(shí)間長(zhǎng)開銷大
  • 只能保證一個(gè)共享變量的原子操作

六. Condition

Condition 用于替代傳統(tǒng)的 Object 的 wait()、notify() 實(shí)現(xiàn)線程間的協(xié)作。

在 Condition 對(duì)象中,與 wait、notify、notifyAll 方法對(duì)應(yīng)的分別是 await、signal 和 signalAll。

Condition 必須要配合 Lock 一起使用,一個(gè) Condition 的實(shí)例必須與一個(gè) Lock 綁定。

它的特性:

  • 一個(gè) Lock 對(duì)象可以創(chuàng)建多個(gè) Condition 實(shí)例,所以可以支持多個(gè)等待隊(duì)列。
  • Condition 在使用 await、signal 或 signalAll 方法時(shí),必須先獲得 Lock 的 lock()
  • 支持響應(yīng)中斷
  • 支持的定時(shí)喚醒功能

七. Semaphore

Semaphore、CountDownLatch、CyclicBarrier 都是并發(fā)工具類。

Semaphore 可以指定多個(gè)線程同時(shí)訪問(wèn)某個(gè)資源,而 synchronized 和 ReentrantLock 都是一次只允許一個(gè)線程訪問(wèn)某個(gè)資源。由于 Semaphore 適用于限制訪問(wèn)某些資源的線程數(shù)目,因此可以使用它來(lái)做限流。

Semaphore 并不會(huì)實(shí)現(xiàn)數(shù)據(jù)的同步,數(shù)據(jù)的同步還是需要使用 synchronized、Lock 等實(shí)現(xiàn)。

它的特性:

  • 基于 AQS 的共享模式
  • 公平性:支持公平模式和非公平模式。默認(rèn)使用了非公平模式。

八. CountDownLatch

CountDownLatch 可以看成是一個(gè)倒計(jì)數(shù)器,它允許一個(gè)或多個(gè)線程等待其他線程完成操作。因此,CountDownLatch 是共享鎖。

CountDownLatch 的 countDown() 方法將計(jì)數(shù)器減1,await() 方法會(huì)阻塞當(dāng)前線程直到計(jì)數(shù)器變?yōu)?。

在我的爬蟲框架NetDiscovery中就使用了 CountDownLatch 來(lái)控制某個(gè)爬蟲的暫停和恢復(fù)。

九. 鎖的分類

Java 鎖的小結(jié).png

十. 總結(jié)

本文小結(jié)了 Java 常用的一些鎖及其一些特性,掌握這些鎖是掌握 Java 并發(fā)編程的基礎(chǔ)。當(dāng)然,Java 的鎖并不止這些,例如 ConcurrentHashMap 的分段鎖(Segment),分布式環(huán)境下所使用的分布式鎖。

參考資料:

  1. 《Java并發(fā)編程藝術(shù)》
  2. 《Java并發(fā)編程實(shí)戰(zhàn)》
  3. 不可不說(shuō)的Java“鎖”事
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容