Java使用讀寫鎖替代同步鎖

應用情景

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

---
在多線程裏就是有個變量,我須要讀取它來判斷是否給它寫入一些信息。
打算加鎖,可是若是讀取時候加入readlock,寫入時候加入writelock,
這樣作可能讀寫不一樣步。可是若是一塊兒加lock效果就跟synchronized同樣,效率變差
---複製代碼

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

  • 高併發的讀寫請求
  • 讀取請求明顯大於寫入的請求
  • 若是用synchronized同步鎖會致使性能降低,原本讀取是能夠多線程同步進行的,同步鎖就只能讓他們一個一個排隊讀取。
  • 若是讀取時加readlock,寫入時候加writelock,會提高效率,由於讀能夠多線程併發,可是在線程A讀完上鎖的毫秒級時間裏,有可能線程B也讀完了,並且搶在了線程A以前修改了變量,致使程序出錯。

處理方案

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

  • 讀鎖:
    • 讀鎖拒絕其餘線程得到寫鎖,讀鎖不拒絕其餘線程得到讀鎖,多個上了讀鎖的線程能夠併發讀不會阻塞。
    • 多個讀鎖同時做用期間,其餘想上寫鎖的線程都處在等待狀態,當最後一個讀鎖釋放後,纔有可能上鎖。
  • 寫鎖:
    • 寫鎖拒絕其餘線程獲取讀鎖和寫鎖。
    • 當一個線程獲取寫鎖後,其餘想要獲取讀寫鎖的線程都處於等待狀態,直到寫鎖釋放纔有可能上鎖。

##代碼實例api

直接拿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阻塞結束,其中一位獲取寫鎖。oracle

相關文章
相關標籤/搜索