併發——讀寫鎖初探

適用場景

  • 讀操做頻發,寫操做不頻繁。
  • 兩個線程同時讀取同一個共享資源沒有任何問題
  • 若是一個線程對共享資源進行寫操做,此時就不能有其餘線程對共享資源進行讀寫

條件分析

  • 寫操做的優先級高於讀操做,在讀操做頻繁的場景下,若是寫操做沒有高於讀操做的優先級,就會致使寫操做線程「餓死」的狀況發生
  • 讀操做觸發條件:函數

    1. 沒有線程正在執行寫操做
    2. 沒有線程在等待執行寫操做
  • 寫操做觸發條件:沒有線程正在執行讀寫操做

代碼實現

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();
  }
}
ReadWriteLockl類中經過讀鎖、寫鎖以兩個鎖的狀態控制線程的讀、寫操做:
writers表示當前正在使用寫鎖的線程數量;
writeRequests表示等待請求寫鎖的線程數量;
readers表示請求讀鎖的線程數量;
說明:
1.線程在獲取讀鎖的時候,只要沒有線程擁有寫鎖即writers==0同時沒有線程請求寫鎖即writerRquests==0,那麼線程就能成功獲取讀鎖;
2.當一個線程想獲取寫鎖的時候,會把寫鎖的請求數加1即writeRequests++,而後再嘗試獲取可否獲取寫鎖,若是當前沒有線程佔用寫鎖即writers==0,那麼此時就能成功獲取寫鎖,同時writers++;若是wirters>0表示寫鎖此時被其餘線程佔用那麼當前線程會被阻塞等待寫鎖釋放時被喚醒。
3.寫操做的優先級高於讀操做的優先級體如今,線程請求讀鎖時會判斷持有寫鎖的線程數和請求寫鎖的線程數,即while(writers > 0 || writeRequests > 0){wait();},而線程請求寫鎖時只須要判斷持有寫鎖和讀鎖的線程數便可,即while(readers > 0 || writers > 0) {wait();}

鎖重入

鎖重入,是指同一線程 外層函數得到鎖以後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。ReentrantLock 和synchronized 都是可重入鎖,可重入鎖最大的做用是避免死鎖。
以自旋鎖爲例,若是自旋鎖不是可重入鎖的話,若是一個線程在第一次獲取鎖執行同步代碼前提下,第二次再執行同步代碼就產生了死鎖。
之前面的代碼爲例:this

  1. 此時有兩個線程Thread1,Thread2
  2. Thread2在Thread1獲取讀鎖之後請求寫鎖,readers=一、writers=0、writeRequests=1
  3. 若此時Thread1再次嘗試獲取同一個讀鎖,根據已有的代碼writers > 0 || writeRequests > 0,由於Thread請求寫鎖的緣由致使該條件成立,Thread1進入阻塞狀態,死鎖出現

讀鎖重入

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;
 }
}
讀鎖的可重入有兩種狀況:
1.當前程序中沒有線程請求寫鎖(這種狀況是幾乎不存在)
2.當前程序中有線程請求寫鎖也有線程請求讀鎖,而且有線程已經獲得了讀鎖

第二種狀況是最多見的,所以咱們須要知道哪些線程是持有讀鎖的

所以在代碼中使用Map來存儲已經持有讀鎖的線程和對應線程獲取讀鎖的次數,經過Map就能夠判斷對應的線程是否持有讀鎖,調整以後的代碼在原有判斷"writeRequests >0"和"writers > 0"還加上了判斷當前線程是否持有讀鎖的判斷邏輯

寫鎖重入

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;
 }
}
寫鎖重入,是在當前程序裏有且只有一個線程持有寫鎖,若是寫鎖重入,說明當前程序中沒有線程持有讀鎖,寫鎖重入只有持有寫鎖的線程才能重入,其餘的線程就須要進入阻塞狀態

讀寫鎖完整代碼

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;
    }
}

參考文獻
http://ifeve.com/read-write-l...線程

相關文章
相關標籤/搜索