併發編程-19AQS同步組件之重入鎖ReentrantLock、 讀寫鎖ReentrantReadWriteLock、Condition


在這裏插入圖片描述

J.U.C腦圖

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述


ReentrantLock概述

重入鎖ReentrantLock,顧名思義,就是支持重進入的鎖,它表示該鎖可以支持一個線程對
資源的重複加鎖,而不會形成本身阻塞本身。
java

重進入是指任意線程在獲取到鎖以後可以再次獲取該鎖而不會被鎖所阻塞git

ReentrantLock雖然沒能像synchronized關鍵字同樣支持隱式的重進入,可是在調用lock()方
法時,已經獲取到鎖的線程,可以再次調用lock()方法獲取鎖而不被阻塞。github


除此以外,該鎖的還支持獲取鎖時的公平和非公平性選擇。實際上,公平的鎖機制每每沒有非公平的效率高,可是,並非任何場景都是以TPS做爲惟一的指標,公平鎖可以減小「飢餓」發生的機率,等待越久的請求越是可以獲得優先知足。看使用場景。安全

公平性鎖保證了鎖的獲取按照FIFO原則,而代價是進行大量的線程切換。非公平性鎖雖然可能形成線程「飢餓」,但極少的線程切換,保證了其更大的吞吐量。markdown


在Java裏一共有兩類鎖, 一類是synchornized同步鎖,還有一種是JUC裏提供的鎖Lock,Lock是個接口,其核心實現類就是ReentrantLock。多線程

ReentrantLock實現 ,主要是採用自旋鎖,循環調用CAS操做來實現加鎖,避免了使線程進入內核態的阻塞狀態併發

ReentrantLock獨有的功能ide

  • 可指定是公平鎖仍是非公平鎖,所謂公平鎖就是先等待的線程先得到鎖
  • 提供了一個Condition類,能夠分組喚醒須要喚醒的線程
  • 提供可以中斷等待鎖的線程的機制,lock.lockInterruptibly()

ReentrantLock 經常使用方法

在這裏插入圖片描述

void  lock()   //加鎖 
void  unlock()  //釋放鎖
boolean isHeldByCurrentThread();   // 當前線程是否保持鎖定
boolean isLocked()  // 是否存在任意線程持有鎖資源
void lockInterruptbly()  // 若是當前線程未被中斷,則獲取鎖定;若是已中斷,則拋出異常(InterruptedException)
int getHoldCount()   // 查詢當前線程保持此鎖定的個數,即調用lock()方法的次數
int getQueueLength()   // 返回正等待獲取此鎖定的預估線程數
int getWaitQueueLength(Condition condition)  // 返回與此鎖定相關的約定condition的線程預估數
boolean hasQueuedThread(Thread thread)  // 當前線程是否在等待獲取鎖資源
boolean hasQueuedThreads()  // 是否有線程在等待獲取鎖資源
boolean hasWaiters(Condition condition)  // 是否存在指定Condition的線程正在等待鎖資源
boolean isFair()   // 是否使用的是公平鎖

synchronized 和 ReentrantLock的比較
synchornized ReentrantLock
可重入性 可重入(都是同一個線程每進入一次,鎖的計數器都自增1,因此要等到鎖的計數器降低爲0時才能釋放鎖) 可重入
鎖的實現 JVM實現,操做系統級別 JDK實現
性能 在引入偏向鎖、輕量級鎖(自旋鎖)後性能大大提高,官方建議無特殊要求時儘可能使用synchornized,而且新版本的一些jdk源碼都由以前的ReentrantLock改爲了synchornized 與優化後的synchornized相差不大
功能區別 方便簡潔,由編譯器負責加鎖和釋放鎖 ,不會產生死鎖 需手工操做鎖的加鎖和釋放,忘記釋放會產生死鎖
鎖粒度 粗粒度,不靈活 細粒度,可靈活控制
能否指定公平鎖 不能夠 能夠
能否放棄鎖 不能夠 能夠

順便說下自旋鎖:是指當一個線程在獲取鎖的時候,若是鎖已經被其它線程獲取,那麼該線程將循環等待,而後不斷的判斷鎖是否可以被成功獲取,直到獲取到鎖纔會退出循環。工具

那該如何選擇呢?性能

若是須要實現ReenTrantLock的三個獨有功能時,就選擇使用ReenTrantLock, 一般狀況下synchronized就可以知足了,並且使用起來簡單,由JVM管理,不會產生死鎖。


ReentrantLock示例

咱們把使用synchronized來確保線程安全的例子,使用ReentrantLock來實現下

在這裏插入圖片描述

屢次運行: 線程安全
在這裏插入圖片描述


讀寫鎖ReentrantReadWriteLock

可重入鎖ReentrantLock是排他鎖,這些鎖在同一時刻只容許一個線程進行訪問

而讀寫鎖ReentrantReadWriteLock在同一時刻能夠容許多個讀線程訪問,可是在寫線程訪問時,全部的讀線程和其餘寫線程均被阻塞。即ReentrantReadWriteLock容許多個讀線程同時訪問,但不容許寫線程和讀線程、寫線程和寫線程同時訪問

在沒有任何讀寫鎖的時候才能取得寫入的鎖,可用於實現悲觀讀取

讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,經過分離讀鎖和寫,使得併發性相比通常的排他鎖有了很大提高。
在這裏插入圖片描述


例子

假設如今有一個類,裏面有一個map集合,但願在對其讀寫的時候可以進行一些線程安全的保護,這時咱們就可使用到ReentrantReadWriteLock

在這裏插入圖片描述


StampedLock

StampedLock是Java8引入的一種新的鎖機制,是讀寫鎖的一個改進版本,讀寫鎖雖然分離了讀和寫的功能,使得讀與讀之間能夠徹底併發,可是讀和寫之間依然是衝突的,讀鎖會徹底阻塞寫鎖,它使用的依然是悲觀的鎖策略。若是有大量的讀線程,它也有可能引發寫線程的飢餓。而StampedLock則提供了一種樂觀的讀策略,這種樂觀策略的鎖很是相似於無鎖的操做,使得樂觀鎖徹底不會阻塞寫線程。

示例

在這裏插入圖片描述

運行結果: 線程安全

在這裏插入圖片描述


Condition

任意一個Java對象,都擁有一組監視器方法(定義在java.lang.Object上),主要包括wait()、 wait(long timeout)、notify()以及notifyAll()方法,這些方法與synchronized同步關鍵字配合,能夠實現等待/通知模式。Condition接口也提供了相似Object的監視器方法,與Lock配合能夠實現等待/通知模式,可是這二者在使用方式以及功能特性上仍是有差異的。

Condition定義了等待/通知兩種類型的方法,當前線程調用這些方法時,須要提早獲取到 Condition對象關聯的鎖。Condition對象是由Lock對象(調用Lock對象的newCondition()方法)建立出來的,換句話說,Condition是依賴Lock對象的。

獲取一個Condition必須經過Lock的newCondition()方法。


示例

Condition是一個多線程間協調通訊的工具類,使得某個或者某些線程一塊兒等待某個條件(Condition),只有當該條件具有( signal 或者 signalAll方法被調用)時 ,這些等待線程纔會被喚醒,從而從新爭奪鎖。

Condition能夠很是靈活的操做線程的喚醒,下面是一個線程等待與喚醒的例子,其中用一、二、三、4序號標出了日誌輸出順序
在這裏插入圖片描述

輸出:

在這裏插入圖片描述


代碼

https://github.com/yangshangwei/ConcurrencyMaster

相關文章
相關標籤/搜索