Java使用讀寫鎖替代同步鎖

塗鴉發表於2017-04-20

應用情景

前一陣有個做反抄襲檢測的小夥伴問了我一個問題。

---
在多執行緒裡就是有個變數,我需要讀取它來判斷是否給它寫入一些資訊。
打算加鎖,但是如果讀取時候加入readlock,寫入時候加入writelock,
這樣做可能讀寫不同步。但是如果一起加lock效果就跟synchronized一樣,效率變差
---複製程式碼

其實他的問題就是下面的場景:

  • 高併發的讀寫請求
  • 讀取請求明顯大於寫入的請求
  • 如果用synchronized同步鎖會導致效能下降,本來讀取是可以多執行緒同步進行的,同步鎖就只能讓他們一個一個排隊讀取。
  • 如果讀取時加readlock,寫入時候加writelock,會提升效率,因為讀可以多執行緒併發,但是線上程A讀完上鎖的毫秒級時間裡,有可能執行緒B也讀完了,而且搶在了執行緒A之前修改了變數,導致程式出錯。

處理方案

我去研究了下讀寫鎖,後來發現其實java官方已經給了答案。使用讀寫鎖加標誌位解決讀寫不同步的問題。Java ReentrantReadWriteLock
在這先普及下讀寫鎖。

  • 讀鎖:
    • 讀鎖拒絕其他執行緒獲得寫鎖,讀鎖不拒絕其他執行緒獲得讀鎖,多個上了讀鎖的執行緒可以併發讀不會阻塞。
    • 多個讀鎖同時作用期間,其他想上寫鎖的執行緒都處在等待狀態,當最後一個讀鎖釋放後,才有可能上鎖。
  • 寫鎖:
    • 寫鎖拒絕其他執行緒獲取讀鎖和寫鎖。
    • 當一個執行緒獲取寫鎖後,其他想要獲取讀寫鎖的執行緒都處於等待狀態,直到寫鎖釋放才有可能上鎖。

##程式碼例項

直接拿Java官方示例解讀了。

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
   void processCachedData() {
     rwl.readLock().lock();  //1. 上讀鎖
     if (!cacheValid) {      //2. 驗證cacheValid
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock(); //3. 解除讀鎖
        rwl.writeLock().lock(); //4. 上寫鎖
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {    //5. 驗證cacheValid
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock(); //6. 上讀鎖
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read //7. 解除寫鎖
        }
     }
     try {
       use(data);
     } finally {
       rwl.readLock().unlock();//8. 解除讀鎖
     }
   }
 }複製程式碼

比如現在有執行緒ABCDE五個執行緒,使用processCachedData()方法,接下來會發現如下步驟。

-> DE執行緒滯後,ABC同時進入到步驟1. 上讀鎖
-> ABC進入到步驟2. 驗證cacheValid。(新例項中cacheValid初始為fasle,所以進入if條件句中)
-> ABC執行步驟3. 解除讀鎖。
-> 假設此時A率先完成步驟4. 上寫鎖。
-> BC無法獲取寫鎖,處於等待狀態,被阻塞在步驟4。 上寫鎖。此時D執行緒執行使用processCachedData()方法,被阻塞在步驟1. 上讀鎖。
-> A進入到步驟5. 驗證cacheValid。
步驟五很關鍵,如果執行緒A寫完後,解除了寫鎖,此時新的執行緒E獲取到了寫鎖,就會寫入新資料,此時就不是同步鎖了,程式出錯。
-> A修改資料,cacheValid置為true。步驟6和步驟7是寫鎖的降級操作,即寫鎖釋放的時候,先降級為讀鎖,這樣其他等待獲取寫鎖的執行緒會繼續等待,然後再釋放寫鎖,保證同步性。
-> A執行完步驟8,BC阻塞結束,其中一位獲取寫鎖。

相關文章