您好,登錄后才能下訂單哦!
這篇“Java讀寫鎖ReentrantReadWriteLock如何使用”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java讀寫鎖ReentrantReadWriteLock如何使用”文章吧。
ReentrantReadWriteLock不知道大家熟悉嗎?其實(shí)在實(shí)際的項(xiàng)目中用的比較少,反正我所在的項(xiàng)目沒有用到過。
ReentrantReadWriteLock稱為讀寫鎖,它提供一個(gè)讀鎖,支持多個(gè)線程共享同一把鎖。它也提供了一把寫鎖,是獨(dú)占鎖,和其他讀鎖或者寫鎖互斥,表明只有一個(gè)線程能持有鎖資源。通過兩把鎖的協(xié)同工作,能夠最大化的提高讀寫的性能,特別是讀多寫少的場(chǎng)景,而往往大部分的場(chǎng)景都是讀多寫少的。
ReentrantReadWriteLock實(shí)現(xiàn)了ReadWriteLock接口,可以獲取到讀鎖(共享鎖),寫鎖(獨(dú)占鎖)。同時(shí),通過構(gòu)造方法可以創(chuàng)建鎖本身是公平鎖還是非公鎖。
讀寫鎖機(jī)制:
讀鎖 | 寫鎖 | |
---|---|---|
讀鎖 | 共享 | 互斥 |
寫鎖 | 互斥 | 互斥 |
線程進(jìn)入讀鎖的前提條件:
沒有其他線程的寫鎖
沒有寫請(qǐng)求,或者有寫請(qǐng)求但調(diào)用線程和持有鎖的線程是同一個(gè)線程
進(jìn)入寫鎖的前提條件:
沒有其他線程的讀鎖
沒有其他線程的寫鎖
鎖升級(jí)、降級(jí)機(jī)制:
我們知道ReentrantLock具備可重入的能力,即同一個(gè)線程多次獲取鎖,不引起阻塞,那么ReentrantReadWriteLock
關(guān)于可重入性是怎么樣的呢?
關(guān)于這個(gè)問題需要引入兩個(gè)概念,鎖升級(jí),鎖降級(jí)。
鎖升級(jí):從讀鎖變成寫鎖。
鎖降級(jí):從寫鎖變成讀鎖;
重入時(shí)鎖升級(jí)不支持:持有讀鎖的情況下去獲取寫鎖會(huì)導(dǎo)致獲取寫鎖永久等待,需要先釋放讀,再去獲得寫
重入時(shí)鎖降級(jí)支持:持有寫鎖的情況下去獲取讀鎖,造成只有當(dāng)前線程會(huì)持有讀鎖,因?yàn)閷戞i會(huì)互斥其他的鎖
API介紹
構(gòu)造方法:
public ReentrantReadWriteLock()
:默認(rèn)構(gòu)造方法,非公平鎖
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 { // 臨界區(qū) } finally { r.unlock(); }
鎖降級(jí)
w.lock(); try { r.lock();// 降級(jí)為讀鎖, 釋放寫鎖, 這樣能夠讓其它線程讀取緩存 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(); }
運(yùn)行結(jié)果:
兩個(gè)線程同時(shí)運(yùn)行,都獲取到了讀鎖
@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(); }
運(yùn)行結(jié)果:
兩個(gè)線程間隔1秒,互斥執(zhí)行
什么場(chǎng)景下讀多寫少? 想必最先想到的就是緩存把,ReentrantReadWriteLock在緩存場(chǎng)景中就是一個(gè)很典型的應(yīng)用。
緩存更新時(shí),是先清緩存還是先更新數(shù)據(jù)庫?
先清緩存:可能造成剛清理緩存還沒有更新數(shù)據(jù)庫,高并發(fā)下,其他線程直接查詢了數(shù)據(jù)庫過期數(shù)據(jù)到緩存中,這種情況非常嚴(yán)重,直接導(dǎo)致后續(xù)所有的請(qǐng)求緩存和數(shù)據(jù)庫不一致。
先更新?lián)欤嚎赡茉斐蓜偢聰?shù)據(jù)庫,還沒清空緩存就有線程從緩存拿到了舊數(shù)據(jù),這種情況概率比較小,影響范圍有限,只對(duì)這一次的查詢結(jié)果有問題。
顯而易見,通常情況下,先更新數(shù)據(jù)庫,然后清空緩存。
public class GenericCachedDao { // 緩存對(duì)象,這里用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(); } //如果緩存沒有命中,從數(shù)據(jù)庫中加載 readWriteLock.writeLock().lock(); try { // 細(xì)節(jié),為防止重復(fù)查詢數(shù)據(jù)庫, 再次驗(yàn)證 // 因?yàn)間et 方法上面部分是可能多個(gè)線程進(jìn)來的, 可能已經(jīng)向緩存填充了數(shù)據(jù) String value = cache.get(key); if(value == null) { // 這里可以改成從數(shù)據(jù)庫查詢 value = "alvin"; cache.put(key, value); } return value; } finally { readWriteLock.writeLock().unlock(); } } // 更新數(shù)據(jù) public void updateData(String key, String value) { // 加寫鎖 readWriteLock.writeLock().lock(); try { // 更新操作TODO // 清空緩存 cache.remove(key); } finally { readWriteLock.writeLock().unlock(); } } }
getData方法是讀取操作,先加讀鎖,從緩存讀取,如果沒有命中,加寫鎖,此時(shí)其他線程就不能讀取了,等寫入成功后,釋放讀鎖。
updateData方法是寫操作,更新時(shí)加寫鎖,其他線程此時(shí)無法讀取,然后清空緩存中的舊數(shù)據(jù)。
以上就是關(guān)于“Java讀寫鎖ReentrantReadWriteLock如何使用”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。