并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition
文章目錄
- J.U.C腦圖
- ReentrantLock概述
- ReentrantLock 常用方法
- synchronized 和 ReentrantLock的比較
- ReentrantLock示例
- 讀寫(xiě)鎖ReentrantReadWriteLock
- 例子
- StampedLock
- 示例
- Condition
- 示例
- 代碼
J.U.C腦圖
ReentrantLock概述
重入鎖ReentrantLock,顧名思義,就是支持重進(jìn)入的鎖,它表示該鎖能夠支持一個(gè)線程對(duì)
資源的重復(fù)加鎖,而不會(huì)造成自己阻塞自己。
重進(jìn)入是指任意線程在獲取到鎖之后能夠再次獲取該鎖而不會(huì)被鎖所阻塞
ReentrantLock雖然沒(méi)能像synchronized關(guān)鍵字一樣支持隱式的重進(jìn)入,但是在調(diào)用lock()方
法時(shí),已經(jīng)獲取到鎖的線程,能夠再次調(diào)用lock()方法獲取鎖而不被阻塞。
除此之外,該鎖的還支持獲取鎖時(shí)的公平和非公平性選擇。實(shí)際上,公平的鎖機(jī)制往往沒(méi)有非公平的效率高,但是,并不是任何場(chǎng)景都是以TPS作為唯一的指標(biāo),公平鎖能夠減少“饑餓”發(fā)生的概率,等待越久的請(qǐng)求越是能夠得到優(yōu)先滿(mǎn)足。看使用場(chǎng)景。
公平性鎖保證了鎖的獲取按照FIFO原則,而代價(jià)是進(jìn)行大量的線程切換。非公平性鎖雖然可能造成線程“饑餓”,但極少的線程切換,保證了其更大的吞吐量。
在Java里一共有兩類(lèi)鎖, 一類(lèi)是synchornized同步鎖,還有一種是JUC里提供的鎖Lock,Lock是個(gè)接口,其核心實(shí)現(xiàn)類(lèi)就是ReentrantLock。
ReentrantLock實(shí)現(xiàn) ,主要是采用自旋鎖,循環(huán)調(diào)用CAS操作來(lái)實(shí)現(xiàn)加鎖,避免了使線程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)
ReentrantLock獨(dú)有的功能
- 可指定是公平鎖還是非公平鎖,所謂公平鎖就是先等待的線程先獲得鎖
- 提供了一個(gè)Condition類(lèi),可以分組喚醒需要喚醒的線程
- 提供能夠中斷等待鎖的線程的機(jī)制,lock.lockInterruptibly()
ReentrantLock 常用方法
void lock() //加鎖 void unlock() //釋放鎖 boolean isHeldByCurrentThread(); // 當(dāng)前線程是否保持鎖定 boolean isLocked() // 是否存在任意線程持有鎖資源 void lockInterruptbly() // 如果當(dāng)前線程未被中斷,則獲取鎖定;如果已中斷,則拋出異常(InterruptedException) int getHoldCount() // 查詢(xún)當(dāng)前線程保持此鎖定的個(gè)數(shù),即調(diào)用lock()方法的次數(shù) int getQueueLength() // 返回正等待獲取此鎖定的預(yù)估線程數(shù) int getWaitQueueLength(Condition condition) // 返回與此鎖定相關(guān)的約定condition的線程預(yù)估數(shù) boolean hasQueuedThread(Thread thread) // 當(dāng)前線程是否在等待獲取鎖資源 boolean hasQueuedThreads() // 是否有線程在等待獲取鎖資源 boolean hasWaiters(Condition condition) // 是否存在指定Condition的線程正在等待鎖資源 boolean isFair() // 是否使用的是公平鎖synchronized 和 ReentrantLock的比較
| 可重入性 | 可重入(都是同一個(gè)線程每進(jìn)入一次,鎖的計(jì)數(shù)器都自增1,所以要等到鎖的計(jì)數(shù)器下降為0時(shí)才能釋放鎖) | 可重入 |
| 鎖的實(shí)現(xiàn) | JVM實(shí)現(xiàn),操作系統(tǒng)級(jí)別 | JDK實(shí)現(xiàn) |
| 性能 | 在引入偏向鎖、輕量級(jí)鎖(自旋鎖)后性能大大提升,官方建議無(wú)特殊要求時(shí)盡量使用synchornized,并且新版本的一些jdk源碼都由之前的ReentrantLock改成了synchornized | 與優(yōu)化后的synchornized相差不大 |
| 功能區(qū)別 | 方便簡(jiǎn)潔,由編譯器負(fù)責(zé)加鎖和釋放鎖 ,不會(huì)產(chǎn)生死鎖 | 需手工操作鎖的加鎖和釋放,忘記釋放會(huì)產(chǎn)生死鎖 |
| 鎖粒度 | 粗粒度,不靈活 | 細(xì)粒度,可靈活控制 |
| 可否指定公平鎖 | 不可以 | 可以 |
| 可否放棄鎖 | 不可以 | 可以 |
順便說(shuō)下自旋鎖:是指當(dāng)一個(gè)線程在獲取鎖的時(shí)候,如果鎖已經(jīng)被其它線程獲取,那么該線程將循環(huán)等待,然后不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會(huì)退出循環(huán)。
那該如何選擇呢?
如果需要實(shí)現(xiàn)ReenTrantLock的三個(gè)獨(dú)有功能時(shí),就選擇使用ReenTrantLock, 通常情況下synchronized就能夠滿(mǎn)足了,而且使用起來(lái)簡(jiǎn)單,由JVM管理,不會(huì)產(chǎn)生死鎖。
ReentrantLock示例
我們把使用synchronized來(lái)確保線程安全的例子,使用ReentrantLock來(lái)實(shí)現(xiàn)下
多次運(yùn)行: 線程安全
讀寫(xiě)鎖ReentrantReadWriteLock
可重入鎖ReentrantLock是排他鎖,這些鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問(wèn)。
而讀寫(xiě)鎖ReentrantReadWriteLock在同一時(shí)刻可以允許多個(gè)讀線程訪問(wèn),但是在寫(xiě)線程訪問(wèn)時(shí),所有的讀線程和其他寫(xiě)線程均被阻塞。即ReentrantReadWriteLock允許多個(gè)讀線程同時(shí)訪問(wèn),但不允許寫(xiě)線程和讀線程、寫(xiě)線程和寫(xiě)線程同時(shí)訪問(wèn)。
在沒(méi)有任何讀寫(xiě)鎖的時(shí)候才能取得寫(xiě)入的鎖,可用于實(shí)現(xiàn)悲觀讀取
讀寫(xiě)鎖維護(hù)了一對(duì)鎖,一個(gè)讀鎖和一個(gè)寫(xiě)鎖,通過(guò)分離讀鎖和寫(xiě),使得并發(fā)性相比一般的排他鎖有了很大提升。
例子
假設(shè)現(xiàn)在有一個(gè)類(lèi),里面有一個(gè)map集合,希望在對(duì)其讀寫(xiě)的時(shí)候能夠進(jìn)行一些線程安全的保護(hù),這時(shí)我們就可以使用到ReentrantReadWriteLock
StampedLock
StampedLock是Java8引入的一種新的鎖機(jī)制,是讀寫(xiě)鎖的一個(gè)改進(jìn)版本,讀寫(xiě)鎖雖然分離了讀和寫(xiě)的功能,使得讀與讀之間可以完全并發(fā),但是讀和寫(xiě)之間依然是沖突的,讀鎖會(huì)完全阻塞寫(xiě)鎖,它使用的依然是悲觀的鎖策略。如果有大量的讀線程,它也有可能引起寫(xiě)線程的饑餓。而StampedLock則提供了一種樂(lè)觀的讀策略,這種樂(lè)觀策略的鎖非常類(lèi)似于無(wú)鎖的操作,使得樂(lè)觀鎖完全不會(huì)阻塞寫(xiě)線程。
示例
運(yùn)行結(jié)果: 線程安全
Condition
任意一個(gè)Java對(duì)象,都擁有一組監(jiān)視器方法(定義在java.lang.Object上),主要包括wait()、 wait(long timeout)、notify()以及notifyAll()方法,這些方法與synchronized同步關(guān)鍵字配合,可以實(shí)現(xiàn)等待/通知模式。Condition接口也提供了類(lèi)似Object的監(jiān)視器方法,與Lock配合可以實(shí)現(xiàn)等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。
Condition定義了等待/通知兩種類(lèi)型的方法,當(dāng)前線程調(diào)用這些方法時(shí),需要提前獲取到 Condition對(duì)象關(guān)聯(lián)的鎖。Condition對(duì)象是由Lock對(duì)象(調(diào)用Lock對(duì)象的newCondition()方法)創(chuàng)建出來(lái)的,換句話(huà)說(shuō),Condition是依賴(lài)Lock對(duì)象的。
獲取一個(gè)Condition必須通過(guò)Lock的newCondition()方法。
示例
Condition是一個(gè)多線程間協(xié)調(diào)通信的工具類(lèi),使得某個(gè)或者某些線程一起等待某個(gè)條件(Condition),只有當(dāng)該條件具備( signal 或者 signalAll方法被調(diào)用)時(shí) ,這些等待線程才會(huì)被喚醒,從而重新?tīng)?zhēng)奪鎖。
Condition可以非常靈活的操作線程的喚醒,下面是一個(gè)線程等待與喚醒的例子,其中用1、2、3、4序號(hào)標(biāo)出了日志輸出順序
輸出:
代碼
https://github.com/yangshangwei/ConcurrencyMaster
總結(jié)
以上是生活随笔為你收集整理的并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 并发编程-17AQS同步组件之 Sema
- 下一篇: 并发编程-21J.U.C组件拓展之Fut