更新時(shí)間:2022-09-23 10:09:08 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽2150次
讀/寫(xiě)鎖是比JavaLock中的鎖文本中顯示的實(shí)現(xiàn)更復(fù)雜的鎖。想象一下,你有一個(gè)應(yīng)用程序讀取和寫(xiě)入一些資源,但寫(xiě)入它的工作不如讀取它。讀取同一資源的兩個(gè)線程不會(huì)互相造成問(wèn)題,因此多個(gè)想要讀取資源的線程同時(shí)被授予訪問(wèn)權(quán)限,重疊。但是,如果單個(gè)線程想要寫(xiě)入資源,則不能同時(shí)進(jìn)行其他讀取或?qū)懭搿榱私鉀Q這個(gè)允許多個(gè)讀者但只有一個(gè)寫(xiě)者的問(wèn)題,你需要一個(gè)讀/寫(xiě)鎖。
首先我們總結(jié)一下獲取資源讀寫(xiě)權(quán)限的條件:
讀取權(quán)限,如果沒(méi)有線程在寫(xiě),并且沒(méi)有線程請(qǐng)求寫(xiě)訪問(wèn)。
寫(xiě)訪問(wèn),如果沒(méi)有線程正在讀取或?qū)懭搿?/p>
如果一個(gè)線程想要讀取資源,只要沒(méi)有線程正在寫(xiě)入,并且沒(méi)有線程請(qǐng)求對(duì)該資源的寫(xiě)訪問(wèn),就可以了。通過(guò)提高寫(xiě)訪問(wèn)請(qǐng)求的優(yōu)先級(jí),我們假設(shè)寫(xiě)請(qǐng)求比讀請(qǐng)求更重要。此外,如果讀取是最常發(fā)生的事情,并且我們沒(méi)有提高寫(xiě)入的優(yōu)先級(jí),則可能會(huì)發(fā)生饑餓。請(qǐng)求寫(xiě)訪問(wèn)的線程將被阻止,直到所有讀者都解鎖了ReadWriteLock. 如果新線程不斷被授予讀訪問(wèn)權(quán)限,則等待寫(xiě)訪問(wèn)權(quán)限的線程將無(wú)限期地保持阻塞,從而導(dǎo)致饑餓。因此,如果當(dāng)前沒(méi)有線程鎖定線程,則只能授予線程讀取訪問(wèn)權(quán)限ReadWriteLock寫(xiě)作,或要求鎖定寫(xiě)作。
可以授予想要對(duì)資源進(jìn)行寫(xiě)訪問(wèn)的線程,因此當(dāng)沒(méi)有線程正在讀取或?qū)懭胭Y源時(shí)。有多少線程請(qǐng)求寫(xiě)訪問(wèn)或按什么順序都沒(méi)有關(guān)系,除非您想保證請(qǐng)求寫(xiě)訪問(wèn)的線程之間的公平性。
考慮到這些簡(jiǎn)單的規(guī)則,我們可以實(shí)現(xiàn)ReadWriteLock如下所示:
公共類讀寫(xiě)鎖{
私人 int 讀者 = 0;
私人 int 作家 = 0;
私人 int writeRequests = 0;
公共同步 void lockRead() 拋出 InterruptedException{
而(作家> 0 || writeRequests > 0){
等待();
}
讀者++;
}
公共同步無(wú)效解鎖讀取(){
讀者——;
通知所有();
}
公共同步 void lockWrite() 拋出 InterruptedException{
寫(xiě)請(qǐng)求++;
而(讀者> 0 ||作家> 0){
等待();
}
寫(xiě)請(qǐng)求——;
作家++;
}
公共同步 void unlockWrite() 拋出 InterruptedException{
作家——;
通知所有();
}
}
有ReadWriteLock兩種鎖定方法和兩種解鎖方法。一種用于讀取訪問(wèn)的鎖定和解鎖方法,一種用于寫(xiě)入訪問(wèn)的鎖定和解鎖方法。
讀取訪問(wèn)的規(guī)則在該lockRead()方法中實(shí)現(xiàn)。所有線程都獲得讀訪問(wèn)權(quán)限,除非有一個(gè)線程具有寫(xiě)訪問(wèn)權(quán)限,或者一個(gè)或多個(gè)線程請(qǐng)求了寫(xiě)訪問(wèn)權(quán)限。
寫(xiě)訪問(wèn)的規(guī)則在lockWrite()方法中實(shí)現(xiàn)。想要寫(xiě)訪問(wèn)的線程從請(qǐng)求寫(xiě)訪問(wèn)開(kāi)始(writeRequests++)。然后它將檢查它是否真的可以獲得寫(xiě)訪問(wèn)權(quán)限。如果沒(méi)有對(duì)資源具有讀訪問(wèn)權(quán)限的線程,并且沒(méi)有對(duì)資源具有寫(xiě)訪問(wèn)權(quán)限的線程,則線程可以獲得寫(xiě)訪問(wèn)權(quán)限。有多少線程請(qǐng)求寫(xiě)訪問(wèn)并不重要。
值得注意的是,兩者都是unlockRead()andunlockWrite()調(diào)用 notifyAll()而不是notify(). 要解釋為什么會(huì)這樣,請(qǐng)想象以下情況:
在 ReadWriteLock 內(nèi)部有等待讀訪問(wèn)的線程和等待寫(xiě)訪問(wèn)的線程。如果被喚醒的線程notify()是讀訪問(wèn)線程,它將被放回等待,因?yàn)橛芯€程在等待寫(xiě)訪問(wèn)。但是,沒(méi)有一個(gè)等待寫(xiě)訪問(wèn)的線程被喚醒,所以沒(méi)有更多的事情發(fā)生。沒(méi)有線程既不能讀也不能寫(xiě)。通過(guò)調(diào)用noftifyAll()喚醒所有等待的線程并檢查它們是否可以獲得所需的訪問(wèn)權(quán)限。
打電話notifyAll()還有另一個(gè)好處。如果多個(gè)線程正在等待讀取訪問(wèn),而沒(méi)有一個(gè)線程正在等待寫(xiě)入訪問(wèn),并且unlockWrite()被調(diào)用,則所有等待讀取訪問(wèn)的線程都被立即授予讀取訪問(wèn)權(quán)限 - 而不是一個(gè)接一個(gè)。
前面顯示的ReadWriteLock類是不可重入的。如果一個(gè)具有寫(xiě)訪問(wèn)權(quán)限的線程再次請(qǐng)求它,它將阻塞,因?yàn)橐呀?jīng)有一個(gè)寫(xiě)者——它自己。此外,考慮這種情況:
線程 1 獲得讀取權(quán)限。
線程 2 請(qǐng)求寫(xiě)訪問(wèn),但由于只有一個(gè)讀取器而被阻止。
線程1重新請(qǐng)求讀訪問(wèn)(重新入鎖),但是因?yàn)橛袑?xiě)請(qǐng)求而被阻塞
在這種情況下,前一個(gè)ReadWriteLock會(huì)鎖定 - 類似于死鎖的情況。不會(huì)授予既不請(qǐng)求讀取也不請(qǐng)求寫(xiě)入訪問(wèn)的線程。
要使ReadWriteLock可重入,有必要進(jìn)行一些更改。讀者和作者的重入將分別處理。
讀取重入
為了讓ReadWriteLock讀者可以重入,我們首先要建立閱讀重入的規(guī)則:
如果線程可以獲得讀取訪問(wèn)權(quán)限(沒(méi)有寫(xiě)入者或?qū)懭胝?qǐng)求),或者如果它已經(jīng)具有讀取訪問(wèn)權(quán)限(無(wú)論寫(xiě)入請(qǐng)求如何),它就會(huì)被授予讀取重入權(quán)限。
為了確定一個(gè)線程是否已經(jīng)具有讀訪問(wèn)權(quán)限,對(duì)每個(gè)被授予讀訪問(wèn)權(quán)限的線程的引用以及它獲得讀鎖的次數(shù)都保存在 Map 中。在確定是否可以授予讀取訪問(wèn)權(quán)限時(shí),將檢查此 Map 是否對(duì)調(diào)用線程的引用。以下是更改后lockRead()andunlockRead()方法的外觀:
公共類讀寫(xiě)鎖{
私有 Map<Thread, Integer> readingThreads =
新的 HashMap<Thread, Integer>();
私人 int 作家 = 0;
私人 int writeRequests = 0;
公共同步 void lockRead() 拋出 InterruptedException{
線程調(diào)用Thread = Thread.currentThread();
而(!canGrantReadAccess(調(diào)用線程)){
等待();
}
readingThreads.put(調(diào)用線程,
(getAccessCount(callingThread) + 1));
}
公共同步無(wú)效解鎖讀取(){
線程調(diào)用Thread = Thread.currentThread();
int accessCount = getAccessCount(callingThread);
if(accessCount == 1){ readingThreads.remove(callingThread); }
否則 { readingThreads.put(callingThread, (accessCount -1)); }
通知所有();
}
私有布爾canGrantReadAccess(線程調(diào)用線程){
如果(作家> 0)返回假;
如果(isReader(調(diào)用線程)返回真;
如果(writeRequests > 0)返回假;
返回真;
}
私有 int getReadAccessCount(線程調(diào)用線程){
整數(shù) accessCount = readingThreads.get(callingThread);
if(accessCount == null) 返回 0;
返回 accessCount.intValue();
}
私有布爾 isReader(線程調(diào)用線程){
返回閱讀Threads.get(callingThread) != null;
}
}
如您所見(jiàn),僅當(dāng)當(dāng)前沒(méi)有線程寫(xiě)入資源時(shí)才授予讀取重入。此外,如果調(diào)用線程已經(jīng)具有讀取訪問(wèn)權(quán)限,則這優(yōu)先于任何 writeRequests。
僅當(dāng)線程已經(jīng)具有寫(xiě)訪問(wèn)權(quán)限時(shí)才授予寫(xiě)重入。以下是更改后的lockWrite()andunlockWrite()方法:
公共類讀寫(xiě)鎖{
私有 Map<Thread, Integer> readingThreads =
新的 HashMap<Thread, Integer>();
私有 int writeAccesses = 0;
私人 int writeRequests = 0;
私有線程寫(xiě)作Thread = null;
公共同步 void lockWrite() 拋出 InterruptedException{
寫(xiě)請(qǐng)求++;
線程調(diào)用Thread = Thread.currentThread();
而(!canGrantWriteAccess(調(diào)用線程)){
等待();
}
寫(xiě)請(qǐng)求——;
寫(xiě)訪問(wèn)++;
寫(xiě)線程 = 調(diào)用線程;
}
公共同步 void unlockWrite() 拋出 InterruptedException{
寫(xiě)訪問(wèn)——;
如果(writeAccesses == 0){
寫(xiě)線程=空;
}
通知所有();
}
私有布爾canGrantWriteAccess(線程調(diào)用線程){
如果(hasReaders())返回假;
if(writingThread == null) 返回真;
if(!isWriter(callingThread)) 返回假;
返回真;
}
私有布爾 hasReaders(){
返回讀數(shù)Threads.size() > 0;
}
私有布爾 isWriter(線程調(diào)用線程){
返回寫(xiě)線程 == 調(diào)用線程;
}
}
請(qǐng)注意,在確定調(diào)用線程是否可以獲得寫(xiě)訪問(wèn)權(quán)時(shí),現(xiàn)在如何考慮當(dāng)前持有寫(xiě)鎖的線程。
有時(shí),具有讀訪問(wèn)權(quán)限的線程也需要獲得寫(xiě)訪問(wèn)權(quán)限。為此,線程必須是唯一的讀者。為了實(shí)現(xiàn)這一點(diǎn),writeLock()應(yīng)該稍微改變方法。這是它的樣子:
公共類讀寫(xiě)鎖{
私有 Map<Thread, Integer> readingThreads =
新的 HashMap<Thread, Integer>();
私有 int writeAccesses = 0;
私人 int writeRequests = 0;
私有線程寫(xiě)作Thread = null;
公共同步 void lockWrite() 拋出 InterruptedException{
寫(xiě)請(qǐng)求++;
線程調(diào)用Thread = Thread.currentThread();
而(!canGrantWriteAccess(調(diào)用線程)){
等待();
}
寫(xiě)請(qǐng)求——;
寫(xiě)訪問(wèn)++;
寫(xiě)線程 = 調(diào)用線程;
}
公共同步 void unlockWrite() 拋出 InterruptedException{
寫(xiě)訪問(wèn)——;
如果(writeAccesses == 0){
寫(xiě)線程=空;
}
通知所有();
}
私有布爾canGrantWriteAccess(線程調(diào)用線程){
if(isOnlyReader(callingThread)) 返回真;
如果(hasReaders())返回假;
if(writingThread == null) 返回真;
if(!isWriter(callingThread)) 返回假;
返回真;
}
私有布爾 hasReaders(){
返回讀數(shù)Threads.size() > 0;
}
私有布爾 isWriter(線程調(diào)用線程){
返回寫(xiě)線程 == 調(diào)用線程;
}
私有布爾 isOnlyReader(線程線程){
返回讀數(shù)Threads.size() == 1 &&
readingThreads.get(callingThread) != null;
}
}
現(xiàn)在ReadWriteLock該類是讀寫(xiě)訪問(wèn)可重入的。
有時(shí),具有寫(xiě)訪問(wèn)權(quán)限的線程也需要讀訪問(wèn)權(quán)限。如果請(qǐng)求,應(yīng)始終授予寫(xiě)入者讀取訪問(wèn)權(quán)限。如果一個(gè)線程有寫(xiě)訪問(wèn)權(quán)限,其他線程就不能有讀或?qū)懺L問(wèn)權(quán)限,所以它并不危險(xiǎn)。以下是該 canGrantReadAccess()方法在更改后的外觀:
公共類讀寫(xiě)鎖{
私有布爾canGrantReadAccess(線程調(diào)用線程){
if(isWriter(callingThread)) 返回真;
如果(寫(xiě)線程!= null)返回false;
如果(isReader(調(diào)用線程)返回真;
如果(writeRequests > 0)返回假;
返回真;
}
}
下面是完全可重入的ReadWriteLock實(shí)現(xiàn)。我對(duì)訪問(wèn)條件進(jìn)行了一些重構(gòu),以使它們更易于閱讀,從而更容易說(shuō)服自己它們是正確的。
公共類讀寫(xiě)鎖{
私有 Map<Thread, Integer> readingThreads =
新的 HashMap<Thread, Integer>();
私有 int writeAccesses = 0;
私人 int writeRequests = 0;
私有線程寫(xiě)作Thread = null;
公共同步 void lockRead() 拋出 InterruptedException{
線程調(diào)用Thread = Thread.currentThread();
而(!canGrantReadAccess(調(diào)用線程)){
等待();
}
readingThreads.put(調(diào)用線程,
(getReadAccessCount(callingThread) + 1));
}
私有布爾canGrantReadAccess(線程調(diào)用線程){
if( isWriter(callingThread) ) 返回真;
if( hasWriter() ) 返回假;
if( isReader(callingThread) ) 返回真;
if( hasWriteRequests() ) 返回假;
返回真;
}
公共同步無(wú)效解鎖讀取(){
線程調(diào)用Thread = Thread.currentThread();
如果(!isReader(調(diào)用線程)){
throw new IllegalMonitorStateException("調(diào)用線程沒(méi)有" +
" 持有此 ReadWriteLock 的讀鎖");
}
int accessCount = getReadAccessCount(callingThread);
if(accessCount == 1){ readingThreads.remove(callingThread); }
否則 { readingThreads.put(callingThread, (accessCount -1)); }
通知所有();
}
公共同步 void lockWrite() 拋出 InterruptedException{
寫(xiě)請(qǐng)求++;
線程調(diào)用Thread = Thread.currentThread();
而(!canGrantWriteAccess(調(diào)用線程)){
等待();
}
寫(xiě)請(qǐng)求——;
寫(xiě)訪問(wèn)++;
寫(xiě)線程 = 調(diào)用線程;
}
公共同步 void unlockWrite() 拋出 InterruptedException{
if(!isWriter(Thread.currentThread()){
throw new IllegalMonitorStateException("調(diào)用線程沒(méi)有" +
" 持有這個(gè) ReadWriteLock 的寫(xiě)鎖");
}
寫(xiě)訪問(wèn)——;
如果(writeAccesses == 0){
寫(xiě)線程=空;
}
通知所有();
}
私有布爾canGrantWriteAccess(線程調(diào)用線程){
if(isOnlyReader(callingThread)) 返回真;
如果(hasReaders())返回假;
if(writingThread == null) 返回真;
if(!isWriter(callingThread)) 返回假;
返回真;
}
私有 int getReadAccessCount(線程調(diào)用線程){
整數(shù) accessCount = readingThreads.get(callingThread);
if(accessCount == null) 返回 0;
返回 accessCount.intValue();
}
私有布爾 hasReaders(){
返回讀數(shù)Threads.size() > 0;
}
私有布爾 isReader(線程調(diào)用線程){
返回閱讀Threads.get(callingThread) != null;
}
私有布爾 isOnlyReader(線程調(diào)用線程){
返回讀數(shù)Threads.size() == 1 &&
readingThreads.get(callingThread) != null;
}
私有布爾 hasWriter(){
返回寫(xiě)作線程!= null;
}
私有布爾 isWriter(線程調(diào)用線程){
返回寫(xiě)線程 == 調(diào)用線程;
}
私有布爾 hasWriteRequests(){
返回 this.writeRequests > 0;
}
}
當(dāng)用 保護(hù)臨界區(qū)時(shí)ReadWriteLock,臨界區(qū)可能會(huì)拋出異常,從 - 子句內(nèi)部調(diào)用readUnlock()和writeUnlock()方法很重要finally。這樣做可以確保ReadWriteLock已解鎖,以便其他線程可以鎖定它。這是一個(gè)例子:
lock.lockWrite();
嘗試{
//做臨界區(qū)代碼,可能會(huì)拋出異常
} 最后 {
lock.unlockWrite();
}
這個(gè)小結(jié)構(gòu)確保ReadWriteLock在關(guān)鍵部分的代碼拋出異常的情況下解鎖。如果unlockWrite() 沒(méi)有從 - 子句內(nèi)部調(diào)用finally,并且從臨界區(qū)拋出異常,ReadWriteLock則將永遠(yuǎn)保持寫(xiě)鎖定,導(dǎo)致調(diào)用該實(shí)例的所有線程lockRead()或lockWrite()在該 ReadWriteLock實(shí)例上無(wú)限期停止。唯一可以解鎖的ReadWriteLock方法是如果 ReadWriteLock是可重入的,并且在拋出異常時(shí)鎖定它的線程后來(lái)成功鎖定它,執(zhí)行關(guān)鍵部分并unlockWrite() 隨后再次調(diào)用。那將ReadWriteLock再次解鎖。但為什么要等到這種情況發(fā)生,如果它發(fā)生了嗎?unlockWrite()從 -子句調(diào)用finally是一個(gè)更健壯的解決方案。
以上就是關(guān)于“Java實(shí)現(xiàn)讀寫(xiě)鎖的原理”介紹,大家如果想了解更多相關(guān)知識(shí),不妨來(lái)關(guān)注一下本站的Java在線學(xué)習(xí),里面的課程內(nèi)容從入門(mén)到精通,細(xì)致全面,很適合沒(méi)有基礎(chǔ)的小伙伴學(xué)習(xí),希望對(duì)大家能夠有所幫助。
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)
初級(jí) 202925
初級(jí) 203221
初級(jí) 202629
初級(jí) 203743