并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition
文章目錄
- J.U.C腦圖
- ReentrantLock概述
- ReentrantLock 常用方法
- synchronized 和 ReentrantLock的比較
- ReentrantLock示例
- 讀寫鎖ReentrantReadWriteLock
- 例子
- StampedLock
- 示例
- Condition
- 示例
- 代碼
J.U.C腦圖
ReentrantLock概述
重入鎖ReentrantLock,顧名思義,就是支持重進入的鎖,它表示該鎖能夠支持一個線程對
資源的重復加鎖,而不會造成自己阻塞自己。
重進入是指任意線程在獲取到鎖之后能夠再次獲取該鎖而不會被鎖所阻塞
ReentrantLock雖然沒能像synchronized關鍵字一樣支持隱式的重進入,但是在調用lock()方
法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞。
除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇。實際上,公平的鎖機制往往沒有非公平的效率高,但是,并不是任何場景都是以TPS作為唯一的指標,公平鎖能夠減少“饑餓”發生的概率,等待越久的請求越是能夠得到優先滿足。看使用場景。
公平性鎖保證了鎖的獲取按照FIFO原則,而代價是進行大量的線程切換。非公平性鎖雖然可能造成線程“饑餓”,但極少的線程切換,保證了其更大的吞吐量。
在Java里一共有兩類鎖, 一類是synchornized同步鎖,還有一種是JUC里提供的鎖Lock,Lock是個接口,其核心實現類就是ReentrantLock。
ReentrantLock實現 ,主要是采用自旋鎖,循環調用CAS操作來實現加鎖,避免了使線程進入內核態的阻塞狀態
ReentrantLock獨有的功能
- 可指定是公平鎖還是非公平鎖,所謂公平鎖就是先等待的線程先獲得鎖
- 提供了一個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的比較
| 可重入性 | 可重入(都是同一個線程每進入一次,鎖的計數器都自增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可以非常靈活的操作線程的喚醒,下面是一個線程等待與喚醒的例子,其中用1、2、3、4序號標出了日志輸出順序
輸出:
代碼
https://github.com/yangshangwei/ConcurrencyMaster
總結
以上是生活随笔為你收集整理的并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 并发编程-17AQS同步组件之 Sema
- 下一篇: 并发编程-21J.U.C组件拓展之Fut