1. 程式人生 > >Java併發程式設計(九)ReentrantReadWriteLock

Java併發程式設計(九)ReentrantReadWriteLock

一、ReentrantReadWriteLock簡介

ReentrantReadWriteLock允許同一時間有一個寫執行緒或多個讀執行緒,滿足了對讀寫併發控制有不同需求的場景,相對於排他鎖,提高了併發性。在實際應用中,大部分情況下對共享資料(如快取)的訪問都是讀操作遠多於寫操作,因此JDK提供了這個讀寫鎖。ReentrantReadWriteLock支援以下特性:

  • 支援公平和非公平的獲取鎖的方式;
  • 支援可重入,讀執行緒在獲取了讀鎖後還可以獲取讀鎖;寫執行緒在獲取了寫鎖之後既可以再次獲取寫鎖又可以獲取讀鎖;
  • 允許從寫入鎖降級為讀取鎖,其實現方式是:先獲取寫入鎖,然後獲取讀取鎖,最後釋放寫入鎖。但是,從讀取鎖升級到寫入鎖是不允許的;
  • 讀取鎖和寫入鎖都支援鎖獲取期間的中斷。

二、ReentrantReadWriteLock搶佔鎖

1、搶佔讀鎖

  • 如果當前寫執行緒已搶佔並且本執行緒不是寫執行緒,那麼失敗。
  • 否則,說明當前沒有寫執行緒或者本執行緒就是寫執行緒(可重入),判斷是否應該讀執行緒阻塞並且讀鎖的個數是否小於最小值,並且CAS成功使讀鎖+1。 
  • 搶佔失敗的原因有三,第一是應該讀執行緒應該阻塞;第二是因為讀鎖達到了上限;第三是因為CAS失敗,有其他執行緒在併發更新state,那麼會調動fullTryAcquireShared方法。

2、搶佔寫鎖

  • 如果當前有寫鎖或者讀鎖。如果只有讀鎖,返回false,因為這時如果可以寫,那麼讀執行緒得到的資料就有可能錯誤;如果有寫鎖,但是執行緒不同,即不符合寫鎖重入規則,返回false 。
  • 如果寫鎖的數量將會超過最大值65535,丟擲異常;否則,寫鎖重入 。
  • 如果沒有讀鎖或寫鎖的話,如果需要阻塞或者CAS失敗,返回false;否則將當前執行緒置為獲得寫鎖的執行緒。

3、小結

  • 如果當前沒有寫鎖或讀鎖時,第一個獲取鎖的執行緒都會成功(當然先CAS),無論該鎖是寫鎖還是讀鎖。 
  • 如果當前已經有了讀鎖,那麼這時獲取寫鎖將失敗,獲取讀鎖有可能成功也有可能失敗。 
  • 如果當前已經有了寫鎖,那麼這時獲取讀鎖或寫鎖,如果執行緒相同(可重入),那麼成功;否則失敗。

三、ReentrantReadWriteLock釋放鎖

1、釋放讀鎖

釋放鎖的第一步是更新計數,接下來進入死迴圈,嘗試更新AQS的狀態,一旦更新成功,則返回;否則,則重試。

2、釋放寫鎖

  • 如果當前沒有執行緒持有寫鎖,但是還要釋放寫鎖,丟擲異常 。
  • 得到解除一把寫鎖後的狀態,如果沒有寫鎖了,那麼將AQS的執行緒置為null 。
  • 不管第二步中是否需要將AQS的執行緒置為null,AQS的狀態總是要更新的。

3、小結

  • 獲取鎖要做的是更改AQS的狀態值以及將需要等待的執行緒放入到佇列中;釋放鎖要做的就是更改AQS的狀態值以及喚醒佇列中的等待執行緒來繼續獲取鎖。
  • 如果當前是寫鎖被佔有了,只有當寫鎖的資料降為0時才認為釋放成功;否則失敗。因為只要有寫鎖,那麼除了佔有寫鎖的那個執行緒,其他執行緒即不可以獲得讀鎖,也不能獲得寫鎖。
  • 如果當前是讀鎖被佔有了,那麼只有在寫鎖的個數為0時才認為釋放成功。因為一旦有寫鎖,別的任何執行緒都不應該再獲得讀鎖了,除了獲得寫鎖的那個執行緒。

 

參考資料:

http://www.cnblogs.com/zaizhoumo/p/7782941.html

https://blog.csdn.net/qq_34144916/article/details/81197830