一文瞭解Java讀寫鎖ReentrantReadWriteLock的使用
概述
ReentrantReadWriteLock不知道大傢熟悉嗎?其實在實際的項目中用的比較少,反正我所在的項目沒有用到過。
ReentrantReadWriteLock稱為讀寫鎖,它提供一個讀鎖,支持多個線程共享同一把鎖。它也提供瞭一把寫鎖,是獨占鎖,和其他讀鎖或者寫鎖互斥,表明隻有一個線程能持有鎖資源。通過兩把鎖的協同工作,能夠最大化的提高讀寫的性能,特別是讀多寫少的場景,而往往大部分的場景都是讀多寫少的。
本文主要講解ReentrantReadWriteLock的使用和應用場景。
ReentrantReadWriteLock介紹
ReentrantReadWriteLock實現瞭ReadWriteLock接口,可以獲取到讀鎖(共享鎖),寫鎖(獨占鎖)。同時,通過構造方法可以創建鎖本身是公平鎖還是非公鎖。
讀寫鎖機制:
讀鎖 | 寫鎖 | |
---|---|---|
讀鎖 | 共享 | 互斥 |
寫鎖 | 互斥 | 互斥 |
線程進入讀鎖的前提條件:
- 沒有其他線程的寫鎖
- 沒有寫請求,或者有寫請求但調用線程和持有鎖的線程是同一個線程
進入寫鎖的前提條件:
- 沒有其他線程的讀鎖
- 沒有其他線程的寫鎖
鎖升級、降級機制:
我們知道ReentrantLock具備可重入的能力,即同一個線程多次獲取鎖,不引起阻塞,那麼ReentrantReadWriteLock
關於可重入性是怎麼樣的呢?
關於這個問題需要引入兩個概念,鎖升級,鎖降級。
- 鎖升級:從讀鎖變成寫鎖。
- 鎖降級:從寫鎖變成讀鎖;
重入時鎖升級不支持:持有讀鎖的情況下去獲取寫鎖會導致獲取寫鎖永久等待,需要先釋放讀,再去獲得寫
重入時鎖降級支持:持有寫鎖的情況下去獲取讀鎖,造成隻有當前線程會持有讀鎖,因為寫鎖會互斥其他的鎖
API介紹
構造方法:
public ReentrantReadWriteLock()
:默認構造方法,非公平鎖public ReentrantReadWriteLock(boolean fair)
:true 為公平鎖
常用API:
public ReentrantReadWriteLock.ReadLock readLock()
:返回讀鎖public ReentrantReadWriteLock.WriteLock writeLock()
:返回寫鎖public void lock()
:加鎖public void unlock()
:解鎖public boolean tryLock()
:嘗試獲取鎖
代碼范式
加解鎖格式
r.lock(); try { // 臨界區 } finally { r.unlock(); }
鎖降級
w.lock(); try { r.lock();// 降級為讀鎖, 釋放寫鎖, 這樣能夠讓其它線程讀取緩存 try { // ... } finally{ w.unlock();// 要在寫鎖釋放之前獲取讀鎖 } } finally{ r.unlock(); }
實戰案例
驗證讀讀共享模式
@Test public void readReadMode() throws InterruptedException { ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock r = rw.readLock(); ReentrantReadWriteLock.WriteLock w = rw.writeLock(); Thread thread0 = new Thread(() -> { r.lock(); try { Thread.sleep(1000); System.out.println("Thread 1 running " + new Date()); } catch (InterruptedException e) { e.printStackTrace(); } finally { r.unlock(); } },"t1"); Thread thread1 = new Thread(() -> { r.lock(); try { Thread.sleep(1000); System.out.println("Thread 2 running " + new Date()); } catch (InterruptedException e) { e.printStackTrace(); } finally { r.unlock(); } },"t2"); thread0.start(); thread1.start(); thread0.join(); thread1.join(); }
運行結果:
兩個線程同時運行,都獲取到瞭讀鎖
驗證讀寫互斥模式
@Test public void readWriteMode() throws InterruptedException { ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock r = rw.readLock(); ReentrantReadWriteLock.WriteLock w = rw.writeLock(); Thread thread0 = new Thread(() -> { r.lock(); try { Thread.sleep(1000); System.out.println("Thread 1 running " + new Date()); } catch (InterruptedException e) { e.printStackTrace(); } finally { r.unlock(); } },"t1"); Thread thread1 = new Thread(() -> { w.lock(); try { Thread.sleep(1000); System.out.println("Thread 2 running " + new Date()); } catch (InterruptedException e) { e.printStackTrace(); } finally { w.unlock(); } },"t2"); thread0.start(); thread1.start(); thread0.join(); thread1.join(); }
運行結果:
兩個線程間隔1秒,互斥執行
真實緩存例子
什麼場景下讀多寫少? 想必最先想到的就是緩存把,ReentrantReadWriteLock在緩存場景中就是一個很典型的應用。
緩存更新時,是先清緩存還是先更新數據庫?
- 先清緩存:可能造成剛清理緩存還沒有更新數據庫,高並發下,其他線程直接查詢瞭數據庫過期數據到緩存中,這種情況非常嚴重,直接導致後續所有的請求緩存和數據庫不一致。
- 先更新據庫:可能造成剛更新數據庫,還沒清空緩存就有線程從緩存拿到瞭舊數據,這種情況概率比較小,影響范圍有限,隻對這一次的查詢結果有問題。
顯而易見,通常情況下,先更新數據庫,然後清空緩存。
public class GenericCachedDao { // 緩存對象,這裡用jvm緩存 Map<String, String> cache = new HashMap<>(); // 讀寫鎖 ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 讀取操作 public String getData(String key) { // 加讀鎖,防止其他線程修改緩存 readWriteLock.readLock().lock(); try { String value = cache.get(key); // 如果緩存命中,返回 if(value != null) { return value; } } finally { // 釋放讀鎖 readWriteLock.readLock().unlock(); } //如果緩存沒有命中,從數據庫中加載 readWriteLock.writeLock().lock(); try { // 細節,為防止重復查詢數據庫, 再次驗證 // 因為get 方法上面部分是可能多個線程進來的, 可能已經向緩存填充瞭數據 String value = cache.get(key); if(value == null) { // 這裡可以改成從數據庫查詢 value = "alvin"; cache.put(key, value); } return value; } finally { readWriteLock.writeLock().unlock(); } } // 更新數據 public void updateData(String key, String value) { // 加寫鎖 readWriteLock.writeLock().lock(); try { // 更新操作TODO // 清空緩存 cache.remove(key); } finally { readWriteLock.writeLock().unlock(); } } }
getData方法是讀取操作,先加讀鎖,從緩存讀取,如果沒有命中,加寫鎖,此時其他線程就不能讀取瞭,等寫入成功後,釋放讀鎖。
updateData方法是寫操作,更新時加寫鎖,其他線程此時無法讀取,然後清空緩存中的舊數據。
到此這篇關於一文瞭解Java讀寫鎖ReentrantReadWriteLock的使用的文章就介紹到這瞭,更多相關Java讀寫鎖ReentrantReadWriteLock內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java 讀寫鎖源碼分析
- java並發編程中ReentrantLock可重入讀寫鎖
- 詳解java中各類鎖的機制
- Java並發之搞懂讀寫鎖
- ReentrantReadWriteLock不能鎖升級的原因總結