首頁  >  文章  >  Java  >  ReadWriteLock介面及其實作ReentrantReadWriteLock

ReadWriteLock介面及其實作ReentrantReadWriteLock

巴扎黑
巴扎黑原創
2017-06-23 16:33:391212瀏覽

  Java並發包的locks包裡的鎖基本上已經介紹得差不多了,ReentrantLock重入鎖是個關鍵,在清楚的了解了同步器AQS的運行機制後,實際上再分析這些鎖就會顯得容易多,這章節主講另外一個重要的鎖-ReentrantReadWriteLock讀寫鎖。

  ReentrantLock是一個獨佔鎖,也就是說只能由一個執行緒取得鎖,但如果場景是執行緒只做讀取的操作呢?這樣ReentrantLock就不是很合適,讀的執行緒並不需要保證其執行緒的安全性,任何一個執行緒都能去獲取鎖,只有這樣才能盡可能保證效能和效率。 ReentrantReadWriteLock就是這樣的一個鎖,在其內部分為讀鎖和寫鎖,可以有N個讀操作線程獲取到寫鎖,但是只能有1個寫操作線程獲取到寫鎖,那麼可以預見的是寫鎖是共享鎖(AQS中的共享模式),讀鎖是獨佔鎖(AQS中的獨佔模式)。首先來看讀寫鎖的介面類別:

1 public interface ReadWriteLock {    
2     Lock readLock();        //获取读锁3     Lock writeLock();        //获取写锁4 }

  可以看到ReadWriteLock介面只定義了兩個方法,取得讀鎖和取得寫鎖的方法。下面是ReadWriteLock的實作類別——ReentrantReadWriteLock。  

  和ReentrantLock類似,ReentrantReadWriteLock在其內部也是透過一個內部類別Sync實現同步器AQS,同樣也是透過實現Sync實現公平鎖和非公平鎖,這一點的思路和ReentrantLock類似。在ReadWriteLock介面中所取得的讀鎖和寫鎖是怎麼實現的呢?

//ReentrantReadWriteLockprivate final ReentrantReadWriteLock.ReadLock readerLock;private final ReentrantReadWriteLock.WriteLock writerLock;final Sync sync;public ReentrantReadWriteLock(){this(false);    //默认非公平锁}public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();    //锁类型(公平/非公平)readerLock = new ReadLock(this);    //构造读锁writerLock = new WriteLock(this);    //构造写锁}
……public ReentrantReadWriteLock.WriteLock writeLock0{return writerLock;}public ReentrantReadWriteLock.ReadLock readLock0{return ReaderLock;}
//ReentrantReadWriteLock$ReadLockpublic static class ReadLock implements Lock {protected ReadLock(ReentrantReadwritLock lock) {
        sync = lock.sync;        //最后还是通过Sync内部类实现锁  }
    ……    //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等}
//ReentrantReadWriteLock$WriteLockpublic static class WriteLock implemnts Lock {protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
  }
……    //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等}

  上面是對ReentrantReadWriteLock做了一個大致的介紹,可以看到在其內部有好幾個內部類,實際上讀寫鎖內有兩個鎖-ReadLock、WriteLock,這兩個鎖都是實作自Lock接口,可以和ReentrantLock對比,而這兩個鎖的內部實作則是透過Sync,也就是同步器AQS實現的,這也可以和ReentrantLock中的Sync比較。
  回顧AQS,其內部有兩個重要的資料結構-一個是同步佇列、一個則是同步狀態,這個同步狀態應用在讀寫鎖中也就是讀寫狀態,但AQS中只有一個state整數型來表示同步狀態,讀寫鎖定中則有讀取、寫入兩個同步狀態需要記錄。所以,讀寫鎖將AQS中的state整型做了一下處理,它是一個int型變數一共4個位元組32位,那麼可以讀寫狀態就可以各佔16位元-高16位表示讀,低16位表示寫。

  

  現在有一個疑問如果state的值位5,二進位為(00000000000000000000000000000101),如何快速確定和寫入各自的狀態呢?這就要用到位移運算了。計算方式為:寫入狀態state & 0x0000FFFF,讀取狀態state >>> 16。寫入狀態增加1等於state + 1,讀取狀態增加1等於state + (1 << 16)。有關移位運算可以參考《<<、>>、>>>移位操作》。

寫鎖的獲取與釋放

  根據我們先前的經驗可以得知:AQS已經將獲取鎖的演算法骨架搭好了,只需子類實現tryAcquire(獨佔鎖),故我們只需查看tryAcquire。

 1 //ReentrantReadWriteLock$Sync 2 protected final boolean tryAcquire(int acquires) { 3     Thread current = Thread.currentThread; 4     int c = getState();    //获取state状态 5     int w = exclusiveCount(c);    //获取写状态,即 state & 0x00001111 6     if (c != 0) {    //存在同步状态(读或写),作下一步判断 7         if (w == 0 || current != getExclusiveOwnerThread())     //写状态为0,但同步状态不为0表示有读状态,此时获取锁失败,或者当前已经有其他写线程获取了锁此时也获取锁失败 8             return false; 9         if (w + exclusiveCount(acquire) > MAX_COUNT)    //锁重入是否超过限制10             throw new Error(“Maxium lock count exceeded”);11         setState(c + acquire);    //记录锁状态12         return true;13   }14   if (writerShouldBlock() || !compareAndSetState(c, c + acquires))15       return false;        //writerShouldBlock对于非公平锁总是返回false,对于公平锁则判断同步队列中是否有前驱节点16   setExclusiveOwnerThread(current);17   return true;18 }

  上面是寫鎖的狀態獲取,不好理解的是writerShouldBlock方法,此方法上面有描述,非公平鎖直接返回false,而對於公平鎖則是調用hasQueuedPredecessors方法如下:

1 //ReentrantReadWriteLock$FairSync2 final boolean writerShouldBlock() {3     return hasQueuedPredecessors();4 }
#

  原因是為什麼呢?這就要回到非公平鎖和公平鎖的區別上來了,簡單回顧一下,詳情可參考《5.Lock介面及其實作ReentrantLock》。對於非公平鎖,每次線程獲取鎖時首先會強行進行鎖獲取操作而不管同步隊列中是否有線程,當獲取不到時才會將線程構造至隊尾;對於公平鎖來講,只要同步隊列中存在線程,就不會去獲取鎖,而是將線程構造加到隊尾。所以重新回到寫入狀態的取得上,tryAcquire方法裡,前面發現沒有線程持有鎖,但是此時會根據鎖的不同做相應操作,對於非公平鎖——搶鎖,對公平鎖——同步隊列中有線程,不搶鎖,添加至隊尾排隊。
  寫鎖的釋放與ReentrantLock的釋放過程基本類似,畢竟都是獨佔鎖,每次釋放減少寫的狀態,直到減小到0就表示寫鎖已經完全釋放。

讀鎖定的獲取與釋放

  同理,根據我們之前的經驗可以得知:AQS已經將獲取鎖的演算法骨架搭好了,只需子類別實作tryAcquireShared(共用鎖定),故我們只需查看tryAcquireShared。我們知道共享模式下的鎖,它能夠被多個線程同時獲取,現在問題來了,T1線程獲取了鎖,同步狀態state=1,此時T2也獲取了鎖,state=2,接著T1線程重入state=3,也就是說讀狀態是所有執行緒讀鎖次數的總和,而每個執行緒各自取得讀鎖的次數只能選擇保存在ThreadLock中,由執行緒自身維護,所以在這個地方要做一些複雜處理,原始碼有點長,但複雜就在於每個執行緒保存自身取得讀鎖的次數,具體參考源碼的tryAcquireShared,仔細閱讀並結合上面對寫鎖獲取的分析不難讀懂。
  讀鎖的釋放值得注意的地方在於自身維護的獲取鎖定的次數,以及透過移位操作減少狀態state – (1 << 16)。

 

以上是ReadWriteLock介面及其實作ReentrantReadWriteLock的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn