一文了解Java讀寫(xiě)鎖ReentrantReadWriteLock的使用
概述
ReentrantReadWriteLock不知道大家熟悉嗎?其實(shí)在實(shí)際的項(xiàng)目中用的比較少,反正我所在的項(xiàng)目沒(méi)有用到過(guò)。
ReentrantReadWriteLock稱(chēng)為讀寫(xiě)鎖,它提供一個(gè)讀鎖,支持多個(gè)線程共享同一把鎖。它也提供了一把寫(xiě)鎖,是獨(dú)占鎖,和其他讀鎖或者寫(xiě)鎖互斥,表明只有一個(gè)線程能持有鎖資源。通過(guò)兩把鎖的協(xié)同工作,能夠最大化的提高讀寫(xiě)的性能,特別是讀多寫(xiě)少的場(chǎng)景,而往往大部分的場(chǎng)景都是讀多寫(xiě)少的。
本文主要講解ReentrantReadWriteLock的使用和應(yīng)用場(chǎng)景。
ReentrantReadWriteLock介紹
ReentrantReadWriteLock實(shí)現(xiàn)了ReadWriteLock接口,可以獲取到讀鎖(共享鎖),寫(xiě)鎖(獨(dú)占鎖)。同時(shí),通過(guò)構(gòu)造方法可以創(chuàng)建鎖本身是公平鎖還是非公鎖。
讀寫(xiě)鎖機(jī)制:
| 讀鎖 | 寫(xiě)鎖 | |
|---|---|---|
| 讀鎖 | 共享 | 互斥 |
| 寫(xiě)鎖 | 互斥 | 互斥 |
線程進(jìn)入讀鎖的前提條件:
- 沒(méi)有其他線程的寫(xiě)鎖
- 沒(méi)有寫(xiě)請(qǐng)求,或者有寫(xiě)請(qǐng)求但調(diào)用線程和持有鎖的線程是同一個(gè)線程
進(jìn)入寫(xiě)鎖的前提條件:
- 沒(méi)有其他線程的讀鎖
- 沒(méi)有其他線程的寫(xiě)鎖
鎖升級(jí)、降級(jí)機(jī)制:
我們知道ReentrantLock具備可重入的能力,即同一個(gè)線程多次獲取鎖,不引起阻塞,那么ReentrantReadWriteLock關(guān)于可重入性是怎么樣的呢?
關(guān)于這個(gè)問(wèn)題需要引入兩個(gè)概念,鎖升級(jí),鎖降級(jí)。
- 鎖升級(jí):從讀鎖變成寫(xiě)鎖。
- 鎖降級(jí):從寫(xiě)鎖變成讀鎖;
重入時(shí)鎖升級(jí)不支持:持有讀鎖的情況下去獲取寫(xiě)鎖會(huì)導(dǎo)致獲取寫(xiě)鎖永久等待,需要先釋放讀,再去獲得寫(xiě)
重入時(shí)鎖降級(jí)支持:持有寫(xiě)鎖的情況下去獲取讀鎖,造成只有當(dāng)前線程會(huì)持有讀鎖,因?yàn)閷?xiě)鎖會(huì)互斥其他的鎖
API介紹
構(gòu)造方法:
public ReentrantReadWriteLock():默認(rèn)構(gòu)造方法,非公平鎖public ReentrantReadWriteLock(boolean fair):true 為公平鎖
常用API:
public ReentrantReadWriteLock.ReadLock readLock():返回讀鎖public ReentrantReadWriteLock.WriteLock writeLock():返回寫(xiě)鎖public void lock():加鎖public void unlock():解鎖public boolean tryLock():嘗試獲取鎖
代碼范式
加解鎖格式
r.lock();
try {
// 臨界區(qū)
} finally {
r.unlock();
}
鎖降級(jí)
w.lock();
try {
r.lock();// 降級(jí)為讀鎖, 釋放寫(xiě)鎖, 這樣能夠讓其它線程讀取緩存
try {
// ...
} finally{
w.unlock();// 要在寫(xiě)鎖釋放之前獲取讀鎖
}
} finally{
r.unlock();
}實(shí)戰(zhàn)案例
驗(yàn)證讀讀共享模式
@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)行,都獲取到了讀鎖
驗(yàn)證讀寫(xiě)互斥模式
@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í)行
真實(shí)緩存例子
什么場(chǎng)景下讀多寫(xiě)少? 想必最先想到的就是緩存把,ReentrantReadWriteLock在緩存場(chǎng)景中就是一個(gè)很典型的應(yīng)用。

緩存更新時(shí),是先清緩存還是先更新數(shù)據(jù)庫(kù)?
- 先清緩存:可能造成剛清理緩存還沒(méi)有更新數(shù)據(jù)庫(kù),高并發(fā)下,其他線程直接查詢(xún)了數(shù)據(jù)庫(kù)過(guò)期數(shù)據(jù)到緩存中,這種情況非常嚴(yán)重,直接導(dǎo)致后續(xù)所有的請(qǐng)求緩存和數(shù)據(jù)庫(kù)不一致。
- 先更新?lián)?kù):可能造成剛更新數(shù)據(jù)庫(kù),還沒(méi)清空緩存就有線程從緩存拿到了舊數(shù)據(jù),這種情況概率比較小,影響范圍有限,只對(duì)這一次的查詢(xún)結(jié)果有問(wèn)題。
顯而易見(jiàn),通常情況下,先更新數(shù)據(jù)庫(kù),然后清空緩存。
public class GenericCachedDao {
// 緩存對(duì)象,這里用jvm緩存
Map<String, String> cache = new HashMap<>();
// 讀寫(xiě)鎖
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();
}
//如果緩存沒(méi)有命中,從數(shù)據(jù)庫(kù)中加載
readWriteLock.writeLock().lock();
try {
// 細(xì)節(jié),為防止重復(fù)查詢(xún)數(shù)據(jù)庫(kù), 再次驗(yàn)證
// 因?yàn)間et 方法上面部分是可能多個(gè)線程進(jìn)來(lái)的, 可能已經(jīng)向緩存填充了數(shù)據(jù)
String value = cache.get(key);
if(value == null) {
// 這里可以改成從數(shù)據(jù)庫(kù)查詢(xún)
value = "alvin";
cache.put(key, value);
}
return value;
} finally {
readWriteLock.writeLock().unlock();
}
}
// 更新數(shù)據(jù)
public void updateData(String key, String value) {
// 加寫(xiě)鎖
readWriteLock.writeLock().lock();
try {
// 更新操作TODO
// 清空緩存
cache.remove(key);
} finally {
readWriteLock.writeLock().unlock();
}
}
}
getData方法是讀取操作,先加讀鎖,從緩存讀取,如果沒(méi)有命中,加寫(xiě)鎖,此時(shí)其他線程就不能讀取了,等寫(xiě)入成功后,釋放讀鎖。
updateData方法是寫(xiě)操作,更新時(shí)加寫(xiě)鎖,其他線程此時(shí)無(wú)法讀取,然后清空緩存中的舊數(shù)據(jù)。
到此這篇關(guān)于一文了解Java讀寫(xiě)鎖ReentrantReadWriteLock的使用的文章就介紹到這了,更多相關(guān)Java讀寫(xiě)鎖ReentrantReadWriteLock內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java并發(fā)讀寫(xiě)鎖ReentrantReadWriteLock 使用場(chǎng)景
- Java中的讀寫(xiě)鎖ReentrantReadWriteLock源碼分析
- Java AQS中ReentrantReadWriteLock讀寫(xiě)鎖的使用
- 詳解Java?ReentrantReadWriteLock讀寫(xiě)鎖的原理與實(shí)現(xiàn)
- ReentrantReadWriteLock?讀寫(xiě)鎖分析總結(jié)
- Java多線程讀寫(xiě)鎖ReentrantReadWriteLock類(lèi)詳解
- Java中ReentrantReadWriteLock讀寫(xiě)鎖的實(shí)現(xiàn)
相關(guān)文章
Java中l(wèi)ist.foreach不能使用字符串拼接的問(wèn)題
這篇文章主要介紹了Java中l(wèi)ist.foreach不能使用字符串拼接的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot使用hutool操作FTP的詳細(xì)過(guò)程
在使用SpringBoot結(jié)合hutool操作FTP時(shí),遇到防火墻導(dǎo)致上傳文件大小為0kb的問(wèn)題,通過(guò)設(shè)置FTP為被動(dòng)模式解決,本文詳細(xì)解析了FTP的主動(dòng)模式和被動(dòng)模式的工作原理、安全性及適用場(chǎng)景,幫助理解FTP的連接方式和解決網(wǎng)絡(luò)限制問(wèn)題2024-09-09
淺析Java類(lèi)和數(shù)據(jù)結(jié)構(gòu)中常用的方法
下面小編就為大家?guī)?lái)一篇淺析Java類(lèi)和數(shù)據(jù)結(jié)構(gòu)中常用的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09
Springboot+AOP實(shí)現(xiàn)返回?cái)?shù)據(jù)提示語(yǔ)國(guó)際化的示例代碼
這篇文章主要介紹了Springboot+AOP實(shí)現(xiàn)返回?cái)?shù)據(jù)提示語(yǔ)國(guó)際化的示例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
IDEA將Maven項(xiàng)目中指定文件夾下的xml等文件編譯進(jìn)classes的方法
這篇文章主要介紹了IDEA將Maven項(xiàng)目中指定文件夾下的xml等文件編譯進(jìn)classes的方法,幫助大家更好的利用IDEA進(jìn)行Java的開(kāi)發(fā)學(xué)習(xí),感興趣的朋友可以了解下2021-01-01
Java 可視化垃圾回收_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Ben Evans是一名資深培訓(xùn)師兼顧問(wèn),他在演講可視化垃圾回收中從基礎(chǔ)談起討論了垃圾回收。以下是對(duì)其演講的簡(jiǎn)短總結(jié)。感興趣的朋友一起學(xué)習(xí)吧2017-05-05
java爬蟲(chóng)Gecco工具抓取新聞實(shí)例
本篇文章主要介紹了JAVA 爬蟲(chóng)Gecco工具抓取新聞實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-10-10

