溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java實現(xiàn)讀寫鎖的原理是什么

發(fā)布時間:2022-09-26 10:37:24 來源:億速云 閱讀:123 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“Java實現(xiàn)讀寫鎖的原理是什么”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Java實現(xiàn)讀寫鎖的原理是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

讀/寫鎖Java實現(xiàn)

首先我們總結(jié)一下獲取資源讀寫權(quán)限的條件:

讀取權(quán)限,如果沒有線程在寫,并且沒有線程請求寫訪問。

寫訪問,如果沒有線程正在讀取或?qū)懭搿?/p>

如果一個線程想要讀取資源,只要沒有線程正在寫入,并且沒有線程請求對該資源的寫訪問,就可以了。通過提高寫訪問請求的優(yōu)先級,我們假設(shè)寫請求比讀請求更重要。此外,如果讀取是最常發(fā)生的事情,并且我們沒有提高寫入的優(yōu)先級,則可能會發(fā)生饑餓。請求寫訪問的線程將被阻止,直到所有讀者都解鎖了ReadWriteLock. 如果新線程不斷被授予讀訪問權(quán)限,則等待寫訪問權(quán)限的線程將無限期地保持阻塞,從而導(dǎo)致饑餓。因此,如果當(dāng)前沒有線程鎖定線程,則只能授予線程讀取訪問權(quán)限ReadWriteLock寫作,或要求鎖定寫作。

可以授予想要對資源進(jìn)行寫訪問的線程,因此當(dāng)沒有線程正在讀取或?qū)懭胭Y源時。有多少線程請求寫訪問或按什么順序都沒有關(guān)系,除非您想保證請求寫訪問的線程之間的公平性。

考慮到這些簡單的規(guī)則,我們可以實現(xiàn)ReadWriteLock如下所示:

公共類讀寫鎖{
  私人 int 讀者 = 0;
  私人 int 作家 = 0;
  私人 int writeRequests = 0;
  公共同步 void lockRead() 拋出 InterruptedException{
    而(作家> 0 || writeRequests > 0){
      等待();
    }
    讀者++;
  }
  公共同步無效解鎖讀?。ǎ﹞
    讀者——;
    通知所有();
  }
  公共同步 void lockWrite() 拋出 InterruptedException{
    寫請求++;
    而(讀者> 0 ||作家> 0){
      等待();
    }
    寫請求——;
    作家++;
  }
  公共同步 void unlockWrite() 拋出 InterruptedException{
    作家——;
    通知所有();
  }
}

有ReadWriteLock兩種鎖定方法和兩種解鎖方法。一種用于讀取訪問的鎖定和解鎖方法,一種用于寫入訪問的鎖定和解鎖方法。

讀取訪問的規(guī)則在該lockRead()方法中實現(xiàn)。所有線程都獲得讀訪問權(quán)限,除非有一個線程具有寫訪問權(quán)限,或者一個或多個線程請求了寫訪問權(quán)限。

寫訪問的規(guī)則在lockWrite()方法中實現(xiàn)。想要寫訪問的線程從請求寫訪問開始(writeRequests++)。然后它將檢查它是否真的可以獲得寫訪問權(quán)限。如果沒有對資源具有讀訪問權(quán)限的線程,并且沒有對資源具有寫訪問權(quán)限的線程,則線程可以獲得寫訪問權(quán)限。有多少線程請求寫訪問并不重要。

值得注意的是,兩者都是unlockRead()andunlockWrite()調(diào)用 notifyAll()而不是notify(). 要解釋為什么會這樣,請想象以下情況:

在 ReadWriteLock 內(nèi)部有等待讀訪問的線程和等待寫訪問的線程。如果被喚醒的線程notify()是讀訪問線程,它將被放回等待,因為有線程在等待寫訪問。但是,沒有一個等待寫訪問的線程被喚醒,所以沒有更多的事情發(fā)生。沒有線程既不能讀也不能寫。通過調(diào)用noftifyAll()喚醒所有等待的線程并檢查它們是否可以獲得所需的訪問權(quán)限。

打電話notifyAll()還有另一個好處。如果多個線程正在等待讀取訪問,而沒有一個線程正在等待寫入訪問,并且unlockWrite()被調(diào)用,則所有等待讀取訪問的線程都被立即授予讀取訪問權(quán)限 - 而不是一個接一個。

讀/寫鎖重入

前面顯示的ReadWriteLock類是不可重入的。如果一個具有寫訪問權(quán)限的線程再次請求它,它將阻塞,因為已經(jīng)有一個寫者——它自己。此外,考慮這種情況:

線程 1 獲得讀取權(quán)限。

線程 2 請求寫訪問,但由于只有一個讀取器而被阻止。

線程1重新請求讀訪問(重新入鎖),但是因為有寫請求而被阻塞

在這種情況下,前一個ReadWriteLock會鎖定 - 類似于死鎖的情況。不會授予既不請求讀取也不請求寫入訪問的線程。

要使ReadWriteLock可重入,有必要進(jìn)行一些更改。讀者和作者的重入將分別處理。

讀取重入

為了讓ReadWriteLock讀者可以重入,我們首先要建立閱讀重入的規(guī)則:

如果線程可以獲得讀取訪問權(quán)限(沒有寫入者或?qū)懭胝埱?,或者如果它已經(jīng)具有讀取訪問權(quán)限(無論寫入請求如何),它就會被授予讀取重入權(quán)限。

為了確定一個線程是否已經(jīng)具有讀訪問權(quán)限,對每個被授予讀訪問權(quán)限的線程的引用以及它獲得讀鎖的次數(shù)都保存在 Map 中。在確定是否可以授予讀取訪問權(quán)限時,將檢查此 Map 是否對調(diào)用線程的引用。以下是更改后lockRead()andunlockRead()方法的外觀:

公共類讀寫鎖{
  私有 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));
  }
  公共同步無效解鎖讀取(){
    線程調(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;
  }
}

如您所見,僅當(dāng)當(dāng)前沒有線程寫入資源時才授予讀取重入。此外,如果調(diào)用線程已經(jīng)具有讀取訪問權(quán)限,則這優(yōu)先于任何 writeRequests。

寫重入

僅當(dāng)線程已經(jīng)具有寫訪問權(quán)限時才授予寫重入。以下是更改后的lockWrite()andunlockWrite()方法:

公共類讀寫鎖{
    私有 Map<Thread, Integer> readingThreads =
        新的 HashMap<Thread, Integer>();
    私有 int writeAccesses = 0;
    私人 int writeRequests = 0;
    私有線程寫作Thread = null;
  公共同步 void lockWrite() 拋出 InterruptedException{
    寫請求++;
    線程調(diào)用Thread = Thread.currentThread();
    而(!canGrantWriteAccess(調(diào)用線程)){
      等待();
    }
    寫請求——;
    寫訪問++;
    寫線程 = 調(diào)用線程;
  }
  公共同步 void unlockWrite() 拋出 InterruptedException{
    寫訪問——;
    如果(writeAccesses == 0){
      寫線程=空;
    }
    通知所有();
  }
  私有布爾canGrantWriteAccess(線程調(diào)用線程){
    如果(hasReaders())返回假;    if(writingThread == null) 返回真;    if(!isWriter(callingThread)) 返回假;
    返回真;
  }
  私有布爾 hasReaders(){
    返回讀數(shù)Threads.size() > 0;
  }
  私有布爾 isWriter(線程調(diào)用線程){
    返回寫線程 == 調(diào)用線程;
  }
}

請注意,在確定調(diào)用線程是否可以獲得寫訪問權(quán)時,現(xiàn)在如何考慮當(dāng)前持有寫鎖的線程。

讀寫重入

有時,具有讀訪問權(quán)限的線程也需要獲得寫訪問權(quán)限。為此,線程必須是唯一的讀者。為了實現(xiàn)這一點,writeLock()應(yīng)該稍微改變方法。這是它的樣子:

公共類讀寫鎖{
    私有 Map<Thread, Integer> readingThreads =
        新的 HashMap<Thread, Integer>();
    私有 int writeAccesses = 0;
    私人 int writeRequests = 0;
    私有線程寫作Thread = null;
  公共同步 void lockWrite() 拋出 InterruptedException{
    寫請求++;
    線程調(diào)用Thread = Thread.currentThread();
    而(!canGrantWriteAccess(調(diào)用線程)){
      等待();
    }
    寫請求——;
    寫訪問++;
    寫線程 = 調(diào)用線程;
  }
  公共同步 void unlockWrite() 拋出 InterruptedException{
    寫訪問——;
    如果(writeAccesses == 0){
      寫線程=空;
    }
    通知所有();
  }
  私有布爾canGrantWriteAccess(線程調(diào)用線程){    if(isOnlyReader(callingThread)) 返回真;
    如果(hasReaders())返回假;    if(writingThread == null) 返回真;    if(!isWriter(callingThread)) 返回假;
    返回真;
  }
  私有布爾 hasReaders(){
    返回讀數(shù)Threads.size() > 0;
  }
  私有布爾 isWriter(線程調(diào)用線程){
    返回寫線程 == 調(diào)用線程;
  }
  私有布爾 isOnlyReader(線程線程){
      返回讀數(shù)Threads.size() == 1 &&
             readingThreads.get(callingThread) != null;
      } 
}

現(xiàn)在ReadWriteLock該類是讀寫訪問可重入的。

寫讀重入

有時,具有寫訪問權(quán)限的線程也需要讀訪問權(quán)限。如果請求,應(yīng)始終授予寫入者讀取訪問權(quán)限。如果一個線程有寫訪問權(quán)限,其他線程就不能有讀或?qū)懺L問權(quán)限,所以它并不危險。以下是該 canGrantReadAccess()方法在更改后的外觀:

公共類讀寫鎖{
    私有布爾canGrantReadAccess(線程調(diào)用線程){      if(isWriter(callingThread)) 返回真;
      如果(寫線程!= null)返回false;
      如果(isReader(調(diào)用線程)返回真;
      如果(writeRequests > 0)返回假;
      返回真;
    }
}

完全可重入讀寫鎖

下面是完全可重入的ReadWriteLock實現(xiàn)。我對訪問條件進(jìn)行了一些重構(gòu),以使它們更易于閱讀,從而更容易說服自己它們是正確的。

公共類讀寫鎖{
  私有 Map<Thread, Integer> readingThreads =
       新的 HashMap<Thread, Integer>();
   私有 int writeAccesses = 0;
   私人 int writeRequests = 0;
   私有線程寫作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() ) 返回假;
    返回真;
  }
  公共同步無效解鎖讀?。ǎ﹞
    線程調(diào)用Thread = Thread.currentThread();
    如果(!isReader(調(diào)用線程)){      throw new IllegalMonitorStateException("調(diào)用線程沒有" +        " 持有此 ReadWriteLock 的讀鎖");
    }    int accessCount = getReadAccessCount(callingThread);    if(accessCount == 1){ readingThreads.remove(callingThread); }
    否則 { readingThreads.put(callingThread, (accessCount -1)); }
    通知所有();
  }
  公共同步 void lockWrite() 拋出 InterruptedException{
    寫請求++;
    線程調(diào)用Thread = Thread.currentThread();
    而(!canGrantWriteAccess(調(diào)用線程)){
      等待();
    }
    寫請求——;
    寫訪問++;
    寫線程 = 調(diào)用線程;
  }
  公共同步 void unlockWrite() 拋出 InterruptedException{    if(!isWriter(Thread.currentThread()){      throw new IllegalMonitorStateException("調(diào)用線程沒有" +        " 持有這個 ReadWriteLock 的寫鎖");
    }
    寫訪問——;
    如果(writeAccesses == 0){
      寫線程=空;
    }
    通知所有();
  }
  私有布爾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(){
    返回寫作線程!= null;
  }
  私有布爾 isWriter(線程調(diào)用線程){
    返回寫線程 == 調(diào)用線程;
  }
  私有布爾 hasWriteRequests(){
      返回 this.writeRequests > 0;
  }
}

從 finally 子句調(diào)用 unlock()

當(dāng)用 保護(hù)臨界區(qū)時ReadWriteLock,臨界區(qū)可能會拋出異常,從 - 子句內(nèi)部調(diào)用readUnlock()和writeUnlock()方法很重要finally。這樣做可以確保ReadWriteLock已解鎖,以便其他線程可以鎖定它。這是一個例子:

lock.lockWrite();
嘗試{  //做臨界區(qū)代碼,可能會拋出異常} 最后 {  lock.unlockWrite();
}

這個小結(jié)構(gòu)確保ReadWriteLock在關(guān)鍵部分的代碼拋出異常的情況下解鎖。如果unlockWrite() 沒有從 - 子句內(nèi)部調(diào)用finally,并且從臨界區(qū)拋出異常,ReadWriteLock則將永遠(yuǎn)保持寫鎖定,導(dǎo)致調(diào)用該實例的所有線程lockRead()或lockWrite()在該 ReadWriteLock實例上無限期停止。唯一可以解鎖的ReadWriteLock方法是如果 ReadWriteLock是可重入的,并且在拋出異常時鎖定它的線程后來成功鎖定它,執(zhí)行關(guān)鍵部分并unlockWrite() 隨后再次調(diào)用。那將ReadWriteLock再次解鎖。但為什么要等到這種情況發(fā)生,如果它發(fā)生了嗎?unlockWrite()從 -子句調(diào)用finally是一個更健壯的解決方案。

讀到這里,這篇“Java實現(xiàn)讀寫鎖的原理是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI