java讀寫鎖ReentrantReadWriteLock實現多並發單利模式__java

來源:互聯網
上載者:User
基本介紹:


讀寫鎖:ReadWriteLock
 
       在多線程的環境下,對同一份資料進行讀寫,會涉及到安全執行緒的問題。比如在一個線程讀取資料的時候,另外一個線程在寫資料,而導致前後資料的不一致性;一個線程在寫資料的時候,另一個線程也在寫,同樣也會導致線程前後看到的資料的不一致性。
 
       這時候可以在讀寫方法中加入互斥鎖,任何時候只能允許一個線程的一個讀或寫操作,而不允許其他線程的讀或寫操作,這樣是可以解決這樣以上的問題,但是效率卻大打折扣了。因為在真實的業務情境中,一份資料,讀取資料的操作次數通常高於寫入資料的操作,而線程與線程間的讀讀操作是不涉及到安全執行緒的問題,沒有必要加入互斥鎖,只要在讀-寫,寫-寫期 間上鎖就行了。

 對於這種情況,讀寫鎖則最好的解決方案。


ReentrantReadWriteLock中定義了2個內部類,ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock,分別用來代表讀取鎖和寫入鎖,ReentrantReadWriteLock對象提供了readLock()和writeLock()方法,用於擷取讀取鎖和寫入鎖
其中: 讀取鎖允許多個reader線程同時持有,而寫入鎖最多隻能有一個writer線程持有。 讀寫鎖的使用場合是:讀取資料的頻率遠大於修改共用資料的頻率。在上述場合下使用讀寫鎖控制共用資源的訪問,可以提高並發效能。 如果一個線程已經持有了寫入鎖,則可以再持有讀寫鎖。相反,如果一個線程已經持有了讀取鎖,則在釋放該讀取鎖之前,不能再持有寫入鎖。 可以調用寫入鎖的newCondition()方法擷取與該寫入鎖綁定的Condition對象,此時與普通的互斥鎖並沒有什麼區別,但是調用讀取鎖的newCondition()方法將拋出異常。

兩種互斥鎖機制:

1、synchronized

2、ReentrantLock

ReentrantLock是jdk5的新特性,採用ReentrantLock可以完全替代替換synchronized傳統的鎖機制,而且採用ReentrantLock的方式更加物件導向,也更加靈活

讀寫鎖的機制:
   "讀-讀"不互斥
   "讀-寫"互斥
   "寫-寫"互斥
 
 即在任何時候必須保證:
   只有一個線程在寫入;
   線程正在讀取的時候,寫入操作等待;
   線程正在寫入的時候,其他線程的寫入操作和讀取操作都要等待;

鎖降級:從寫鎖變成讀鎖;鎖定擴大:從讀鎖變成寫鎖。讀鎖是可以被多線程共用的,寫鎖是單線程獨佔的。也就是說寫鎖的並發限制比讀鎖高

如下代碼會產生死結,因為同一個線程中,在沒有釋放讀鎖的情況下,就去申請寫鎖,這屬於鎖定擴大,ReentrantReadWriteLock是不支援的。

 
ReadWriteLock rtLock = new ReentrantReadWriteLock();rtLock.readLock().lock();System.out.println("get readLock.");rtLock.writeLock().lock();System.out.println("blocking");</span></span>



ReentrantReadWriteLock支援鎖降級,如下代碼不會產生死結。

 
ReadWriteLock rtLock = new ReentrantReadWriteLock();rtLock.writeLock().lock();System.out.println("writeLock");rtLock.readLock().lock();System.out.println("get read lock");


這段代碼雖然不會導致死結,但沒有正確的釋放鎖。從寫鎖降級成讀鎖,並不會自動釋放當前線程擷取的寫鎖,仍然需要顯示的釋放,否則別的線程永遠也擷取不到寫鎖。鎖的釋放和擷取可以看下:可重新進入鎖的擷取和釋放需要注意的一點兒事

 
<span style="white-space:pre"></span>final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();Thread wt = new Thread(new Runnable(){public void run(){readWriteLock.writeLock().lock();System.out.println("writeLock");readWriteLock.readLock().lock();System.out.println("readLock");readWriteLock.readLock().unlock();System.out.println("block");}});wt.start();try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("main blocking.");readWriteLock.readLock().lock();</span>




應用情境:

讀寫鎖的適用情境 
讀多寫少的高並發環境下,可以說這個情境算是最適合使用ReadWriteLock 了。 


單利模式代碼:


import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;public class Singleton { private static Singleton instance = null;      private static ReadWriteLock rwl = new ReentrantReadWriteLock();      private Singleton(){      }       /**      * 當前線程在擷取到寫鎖的過程中,可以擷取到讀鎖,這叫鎖的重入,然後導致了寫鎖的降級,稱為降級鎖。 從 3 獲得寫鎖 到 5變成讀鎖 降級                 利用重入可以將寫鎖降級,但只能在當前線程保持的所有寫入鎖都已經釋放後,才允許重入 reader使用它們。  6                 所以在重入的過程中,其他的線程不會有擷取到鎖的機會(這樣做的好處)。試想,先釋放寫鎖,在上讀鎖,這樣做有什麼弊端。(如果5和6顛倒)                  如果這樣做,那麼在釋放寫鎖後,在得到讀鎖前,有可能被其他線程打斷。                  重入————>降級鎖的步驟:先擷取寫入鎖3,然後擷取讀取鎖5,最後釋放寫入鎖6(重點)      * @return      */    public static Singleton getInstance(){          rwl.readLock().lock();  //1        try          {              if (null == instance)              {                  rwl.readLock().unlock();  //2                rwl.writeLock().lock();  //3                if (null == instance)  //4                {                      instance = new Singleton();                  }                  rwl.readLock().lock();  //5                rwl.writeLock().unlock();  //6            }          }          finally          {              rwl.readLock().unlock();  //7        }          return instance;      }  }


程式碼分析:

 當有n多線程 使用同Singleton 執行個體對象 調用getInstance方法時,就會產生線程的並發問題.
   @1行,當有線程正在對資料進行 寫操作的時候,運行到@1行的線程要等待 寫操作的完成,因為第一個運行到@3的線程會加上鎖,然後對資料進行需該,期間不允許任何線程進行讀或者是寫的操作,


   當寫完後,在該線程上加上讀鎖操作,以防止解寫鎖後,別的線程對資料再次進行寫時出錯.在第一個運行到@3的線程之後的很多線程,


   可能已經運行到了@2,當對資料修改好之後,解除掉寫鎖,別的線程就會執行到@3,這時第一個線程已經經資料修改好了,所以有了@4的判斷。


   在編寫多線程程式的時候,要置於並發線程的環境下考慮,巧妙的運用ReentrantReadWriteLock,在運用時,注意鎖的降級,寫入鎖可以獲得讀鎖,讀鎖不可以獲得寫入鎖,所以在上寫入鎖時,必須先將讀鎖進行解除,然後上讀鎖。


   使用時注意的幾個方面:
       讀鎖是排寫鎖操作的,讀鎖不排讀鎖操作,多個讀鎖可以並發不阻塞。即在讀鎖擷取後和讀鎖釋放之前,寫鎖並不能被任何線程獲得,
       多個讀鎖同時作用期間,試圖擷取寫鎖的線程都處於等待狀態,當最後一個讀鎖釋放後,試圖擷取寫鎖的線程才有機會擷取寫鎖。
       寫鎖是排寫鎖、排讀鎖操作的。當一個線程擷取到寫鎖之後,其他試圖擷取寫鎖和試圖擷取讀鎖的線程都處於等待狀態,直到寫鎖被釋放。
         寫鎖是可以獲得讀鎖的,即:
         rwl.writeLock().lock();
         //在寫鎖狀態中,可以擷取讀鎖
         rwl.readLock().lock();
         rwl.writeLock().unlock();
     讀鎖是不能夠獲得寫鎖的,如果要加寫鎖,本線程必須釋放所持有的讀鎖,即:
          rwl.readLock().lock();
         //......
         //必須釋放掉讀鎖,才能夠加寫鎖
         rwl.readLock().unlock();
         rwl.writeLock().lock();


當前線程在擷取到寫鎖的過程中,可以擷取到讀鎖,這叫鎖的重入,然後導致了寫鎖的降級,稱為降級鎖。 

從 3 獲得寫鎖 到 5變成讀鎖降級
利用重入可以將寫鎖降級,但只能在當前線程保持的所有寫入鎖都已經釋放後,才允許重入 reader使用它們。  6
所以在重入的過程中,其他的線程不會有擷取到鎖的機會(這樣做的好處)。試想,先釋放寫鎖,在上讀鎖,這樣做有什麼弊端。(如果5和6顛倒)
如果這樣做,那麼在釋放寫鎖後,在得到讀鎖前,有可能被其他線程打斷。
重入————>降級鎖的步驟:先擷取寫入鎖3,然後擷取讀取鎖5,最後釋放寫入鎖6(重點)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.