程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java多線程-讀寫鎖道理

java多線程-讀寫鎖道理

編輯:關於JAVA

java多線程-讀寫鎖道理。本站提示廣大學習愛好者:(java多線程-讀寫鎖道理)文章只能為提供參考,不一定能成為您想要的結果。以下是java多線程-讀寫鎖道理正文


Java5 在 java.util.concurrent 包中曾經包括了讀寫鎖。雖然如斯,我們照樣應當懂得其完成面前的道理。

  1. 讀/寫鎖的 Java 完成(Read / Write Lock Java Implementation)
  2. 讀/寫鎖的重入(Read / Write Lock Reentrance)
  3. 讀鎖重入(Read Reentrance)
  4. 寫鎖重入(Write Reentrance)
  5. 讀鎖進級到寫鎖(Read to Write Reentrance)
  6. 寫鎖升級到讀鎖(Write to Read Reentrance)
  7. 可重入的 ReadWriteLock 的完全完成(Fully Reentrant ReadWriteLock)
  8. 在 finally 中挪用 unlock() (Calling unlock() from a finally-clause)
  9. 讀/寫鎖的 Java 完成

    先讓我們對讀寫拜訪資本的前提做個概述:

    讀取 沒有線程正在做寫操作,且沒有線程在要求寫操作。

    寫入 沒有線程正在做讀寫操作。

    假如某個線程想要讀取資本,只需沒有線程正在對該資本停止寫操作且沒有線程要求對該資本的寫操作便可。我們假定對寫操作的要求比對讀操作的要求更主要,就要晉升寫要求的優先級。另外,假如讀操作產生的比擬頻仍,我們又沒有晉升寫操作的優先級,那末就會發生“饑餓”景象。要求寫操作的線程會一向壅塞,直到一切的讀線程都從 ReadWriteLock 上解鎖了。假如一向包管新線程的讀操作權限,那末期待寫操作的線程就會一向壅塞下去,成果就是產生“饑餓”。是以,只要當沒有線程正在鎖住 ReadWriteLock 停止寫操作,且沒有線程要求該鎖預備履行寫操作時,能力包管讀操作持續。

    當其它線程沒有對同享資本停止讀操作或許寫操作時,某個線程就有能夠取得該同享資本的寫鎖,進而對同享資本停止寫操作。有若干線程要求了寫鎖和以何種次序要求寫鎖其實不主要,除非你想包管寫鎖要求的公正性。

    依照下面的論述,簡略的完成出一個讀/寫鎖,代碼以下

    public class ReadWriteLock{
     private int readers = 0;
     private int writers = 0;
     private int writeRequests = 0;
    
     public synchronized void lockRead() 
      throws InterruptedException{
      while(writers > 0 || writeRequests > 0){
       wait();
      }
      readers++;
     }
    
     public synchronized void unlockRead(){
      readers--;
      notifyAll();
     }
    
     public synchronized void lockWrite() 
      throws InterruptedException{
      writeRequests++;
    
      while(readers > 0 || writers > 0){
       wait();
      }
      writeRequests--;
      writers++;
     }
    
     public synchronized void unlockWrite() 
      throws InterruptedException{
      writers--;
      notifyAll();
     }
    }
    
    

    ReadWriteLock 類中,讀鎖和寫鎖各有一個獲得鎖和釋放鎖的辦法。

    讀鎖的完成在 lockRead()中,只需沒有線程具有寫鎖(writers==0),且沒有線程在要求寫鎖(writeRequests ==0),一切想取得讀鎖的線程都能勝利獲得。

    寫鎖的完成在 lockWrite()中,當一個線程想取得寫鎖的時刻,起首會把寫鎖要求數加 1(writeRequests++),然後再去斷定能否可以或許真能取得寫鎖,當沒有線程持有讀鎖(readers==0 ),且沒有線程持有寫鎖(writers==0)時就可以取得寫鎖。有若干線程在要求寫鎖並沒有關系。

    須要留意的是,在兩個釋放鎖的辦法(unlockRead,unlockWrite)中,都挪用了 notifyAll 辦法,而不是 notify。要說明這個緣由,我們可以想象上面一種情況:

    假如有線程在期待獲得讀鎖,同時又有線程在期待獲得寫鎖。假如這時候個中一個期待讀鎖的線程被 notify 辦法叫醒,但由於此時仍有要求寫鎖的線程存在(writeRequests>0),所以被叫醒的線程會再次進入壅塞狀況。但是,期待寫鎖的線程一個也沒被叫醒,就像甚麼也沒產生過一樣(譯者注:旌旗燈號喪失景象)。假如用的是 notifyAll 辦法,一切的線程都邑被叫醒,然後斷定可否取得其要求的鎖。

    用 notifyAll 還有一個利益。假如有多個讀線程在期待讀鎖且沒有線程在期待寫鎖時,挪用 unlockWrite()後,一切期待讀鎖的線程都能立馬勝利獲得讀鎖 —— 而不是一次只許可一個。

    讀/寫鎖的重入

    下面完成的讀/寫鎖(ReadWriteLock) 是弗成重入的,當一個曾經持有寫鎖的線程再次要求寫鎖時,就會被壅塞。緣由是曾經有一個寫線程了——就是它本身。另外,斟酌上面的例子:

    1. Thread 1 取得了讀鎖。
    2. Thread 2 要求寫鎖,但由於 Thread 1 持有了讀鎖,所以寫鎖要求被壅塞。
    3. Thread 1 再想要求一次讀鎖,但由於 Thread 2 處於要求寫鎖的狀況,所以想再次獲得讀鎖也會被壅塞。 下面這類情況應用後面的 ReadWriteLock 就會被鎖定——一品種似於逝世鎖的情況。不會再有線程可以或許勝利獲得讀鎖或寫鎖了。
    4. 為了讓 ReadWriteLock 可重入,須要對它做一些改良。上面會分離處置讀鎖的重入和寫鎖的重入。

      讀鎖重入

      為了讓 ReadWriteLock 的讀鎖可重入,我們要先為讀鎖重入樹立規矩:

      要包管某個線程中的讀鎖可重入,要末知足獲得讀鎖的前提(沒有寫或寫要求),要末曾經持有讀鎖(不論能否有寫要求)。 要肯定一個線程能否曾經持有讀鎖,可以用一個 map 來存儲曾經持有讀鎖的線程和對應線程獲得讀鎖的次數,當須要斷定某個線程可否取得讀鎖時,就應用 map 中存儲的數據停止斷定。上面是辦法 lockRead 和 unlockRead 修正後的的代碼:

      public class ReadWriteLock{
       private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();
      
       private int writers = 0;
       private int writeRequests = 0;
      
       public synchronized void lockRead() 
        throws InterruptedException{
        Thread callingThread = Thread.currentThread();
        while(! canGrantReadAccess(callingThread)){
         wait();                 
        }
      
        readingThreads.put(callingThread,
         (getAccessCount(callingThread) + 1));
       }
      
       public synchronized void unlockRead(){
        Thread callingThread = Thread.currentThread();
        int accessCount = getAccessCount(callingThread);
        if(accessCount == 1) { 
         readingThreads.remove(callingThread); 
        } else {
         readingThreads.put(callingThread, (accessCount -1)); 
        }
        notifyAll();
       }
      
       private boolean canGrantReadAccess(Thread callingThread){
        if(writers > 0) return false;
        if(isReader(callingThread) return true;
        if(writeRequests > 0) return false;
        return true;
       }
      
       private int getReadAccessCount(Thread callingThread){
        Integer accessCount = readingThreads.get(callingThread);
        if(accessCount == null) return 0;
        return accessCount.intValue();
       }
      
       private boolean isReader(Thread callingThread){
        return readingThreads.get(callingThread) != null;
       }
      }

      代碼中我們可以看到,只要在沒有線程具有寫鎖的情形下才許可讀鎖的重入。另外,重入的讀鎖比寫鎖優先級高。

      寫鎖重入

      僅當一個線程曾經持有寫鎖,才許可寫鎖重入(再次取得寫鎖)。上面是辦法 lockWrite 和 unlockWrite 修正後的的代碼。

      public class ReadWriteLock{
       private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();
      
       private int writeAccesses = 0;
       private int writeRequests = 0;
       private Thread writingThread = null;
      
       public synchronized void lockWrite() 
        throws InterruptedException{
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while(!canGrantWriteAccess(callingThread)){
         wait();
        }
        writeRequests--;
        writeAccesses++;
        writingThread = callingThread;
       }
      
       public synchronized void unlockWrite() 
        throws InterruptedException{
        writeAccesses--;
        if(writeAccesses == 0){
         writingThread = null;
        }
        notifyAll();
       }
      
       private boolean canGrantWriteAccess(Thread callingThread){
        if(hasReaders()) return false;
        if(writingThread == null) return true;
        if(!isWriter(callingThread)) return false;
        return true;
       }
      
       private boolean hasReaders(){
        return readingThreads.size() > 0;
       }
      
       private boolean isWriter(Thread callingThread){
        return writingThread == callingThread;
       }
      }
      
      

      留意在肯定以後線程能否可以或許獲得寫鎖的時刻,是若何處置的。

      讀鎖進級到寫鎖

      有時,我們願望一個具有讀鎖的線程,也能取得寫鎖。想要許可如許的操作,請求這個線程是獨一一個具有讀鎖的線程。writeLock()須要做點修改來到達這個目標:

      public class ReadWriteLock{
       private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();
      
       private int writeAccesses = 0;
       private int writeRequests = 0;
       private Thread writingThread = null;
      
       public synchronized void lockWrite() 
        throws InterruptedException{
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while(!canGrantWriteAccess(callingThread)){
         wait();
        }
        writeRequests--;
        writeAccesses++;
        writingThread = callingThread;
       }
      
       public synchronized void unlockWrite() throws InterruptedException{
        writeAccesses--;
        if(writeAccesses == 0){
         writingThread = null;
        }
        notifyAll();
       }
      
       private boolean canGrantWriteAccess(Thread callingThread){
        if(isOnlyReader(callingThread)) return true;
        if(hasReaders()) return false;
        if(writingThread == null) return true;
        if(!isWriter(callingThread)) return false;
        return true;
       }
      
       private boolean hasReaders(){
        return readingThreads.size() > 0;
       }
      
       private boolean isWriter(Thread callingThread){
        return writingThread == callingThread;
       }
      
       private boolean isOnlyReader(Thread thread){
        return readers == 1 && readingThreads.get(callingThread) != null;
       }
      }
      
      

      如今 ReadWriteLock 類便可以從讀鎖進級到寫鎖了。

      寫鎖升級到讀鎖

      有時具有寫鎖的線程也願望獲得讀鎖。假如一個線程具有了寫鎖,那末天然其它線程是弗成能具有讀鎖或寫鎖了。所以關於一個具有寫鎖的線程,再取得讀鎖,是不會有甚麼風險的。我們僅僅須要對下面 canGrantReadAccess 辦法停止簡略地修正:

      public class ReadWriteLock{
       private boolean canGrantReadAccess(Thread callingThread){
        if(isWriter(callingThread)) return true;
        if(writingThread != null) return false;
        if(isReader(callingThread) return true;
        if(writeRequests > 0) return false;
        return true;
       }
      }
      

      可重入的 ReadWriteLock 的完全完成

      上面是完全的 ReadWriteLock 完成。為了便於代碼的浏覽與懂得,簡略對下面的代碼做了重構。重構後的代碼以下。

      public class ReadWriteLock{
       private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();
      
       private int writeAccesses = 0;
       private int writeRequests = 0;
       private Thread writingThread = null;
      
       public synchronized void lockRead() 
        throws InterruptedException{
        Thread callingThread = Thread.currentThread();
        while(! canGrantReadAccess(callingThread)){
         wait();
        }
      
        readingThreads.put(callingThread,
         (getReadAccessCount(callingThread) + 1));
       }
      
       private boolean canGrantReadAccess(Thread callingThread){
        if(isWriter(callingThread)) return true;
        if(hasWriter()) return false;
        if(isReader(callingThread)) return true;
        if(hasWriteRequests()) return false;
        return true;
       }
      
       public synchronized void unlockRead(){
        Thread callingThread = Thread.currentThread();
        if(!isReader(callingThread)){
         throw new IllegalMonitorStateException(
          "Calling Thread does not" +
          " hold a read lock on this ReadWriteLock");
        }
        int accessCount = getReadAccessCount(callingThread);
        if(accessCount == 1){ 
         readingThreads.remove(callingThread); 
        } else { 
         readingThreads.put(callingThread, (accessCount -1));
        }
        notifyAll();
       }
      
       public synchronized void lockWrite() 
        throws InterruptedException{
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while(!canGrantWriteAccess(callingThread)){
         wait();
        }
        writeRequests--;
        writeAccesses++;
        writingThread = callingThread;
       }
      
       public synchronized void unlockWrite() 
        throws InterruptedException{
        if(!isWriter(Thread.currentThread()){
        throw new IllegalMonitorStateException(
         "Calling Thread does not" +
         " hold the write lock on this ReadWriteLock");
        }
        writeAccesses--;
        if(writeAccesses == 0){
         writingThread = null;
        }
        notifyAll();
       }
      
       private boolean canGrantWriteAccess(Thread callingThread){
        if(isOnlyReader(callingThread)) return true;
        if(hasReaders()) return false;
        if(writingThread == null) return true;
        if(!isWriter(callingThread)) return false;
        return true;
       }
      
       private int getReadAccessCount(Thread callingThread){
        Integer accessCount = readingThreads.get(callingThread);
        if(accessCount == null) return 0;
        return accessCount.intValue();
       }
      
       private boolean hasReaders(){
        return readingThreads.size() > 0;
       }
      
       private boolean isReader(Thread callingThread){
        return readingThreads.get(callingThread) != null;
       }
      
       private boolean isOnlyReader(Thread callingThread){
        return readingThreads.size() == 1 &&
         readingThreads.get(callingThread) != null;
       }
      
       private boolean hasWriter(){
        return writingThread != null;
       }
      
       private boolean isWriter(Thread callingThread){
        return writingThread == callingThread;
       }
      
       private boolean hasWriteRequests(){
        return this.writeRequests > 0;
       }
      }
      
      

      在 finally 中挪用 unlock()

      在應用 ReadWriteLock 來掩護臨界區時,假如臨界區能夠拋出異常,在 finally 塊中挪用 readUnlock()和 writeUnlock()就顯得很主要了。如許做是為了包管 ReadWriteLock 能被勝利解鎖,然後其它線程可以要求到該鎖。這裡有個例子:

      lock.lockWrite();
      try{
       //do critical section code, which may throw exception
      } finally {
       lock.unlockWrite();
      }
      

      下面如許的代碼構造可以或許包管臨界區中拋出異常時 ReadWriteLock 也會被釋放。假如 unlockWrite 辦法不是在 finally 塊中挪用的,當臨界區拋出了異常時,ReadWriteLock 會一向堅持在寫鎖定狀況,就會招致一切挪用 lockRead()或 lockWrite()的線程一向壅塞。獨一可以或許從新解鎖 ReadWriteLock 的身分能夠就是 ReadWriteLock 是可重入的,當拋出異常時,這個線程後續還可以勝利獲得這把鎖,然後履行臨界區和再次挪用 unlockWrite(),這就會再次釋放 ReadWriteLock。然則假如該線程後續不再獲得這把鎖了呢?所以,在 finally 中挪用 unlockWrite 對寫出硬朗代碼是很主要的。

       以上就是對java  多線程的材料整頓,後續持續彌補相干材料,感謝年夜家對本站的支撐!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved