讀寫鎖ReentrantReadWriteLock

u010043421發表於2015-09-28
讀鎖為共享鎖,而寫鎖則為獨佔鎖,同時他們用的是一個同步器,同步器中不同的內部類實現了相應的功能
AQS中有一個state欄位(int型別,32位)用來描述有多少執行緒獲持有鎖。在獨佔鎖的時代這個值通常是0或者1(如果是重入的就是重入的次數),在共享鎖的時代就是持有鎖的數量。ReadWriteLock的讀、寫鎖是相關但是又不一致的,所以需要兩個數來描述讀鎖(共享鎖)和寫鎖(獨佔鎖)的數量。顯然現在一個state就不夠用了。於是在ReentrantReadWrilteLock裡面將這個欄位一分為二,高位16位表示共享鎖的數量,低位16位表示獨佔鎖的數量(或者重入數量)。2^16-1=65536,這就是上節中提到的為什麼共享鎖和獨佔鎖的數量最大隻能是65535的原因了。


1.在獨佔數量上則採用了return c & EXCLUSIVE_MASK  
EXCLUSIVE_MASK  = (1 << SHARED_SHIFT) - 1; 表示向左移動16位 然後減掉1,即低16位全為1,高16位全為0
即通過 & 00000000000000001111111111111111的形式獲取低16位
通過與操作就能知道states變數的低16的值,也即獨佔鎖數量
2.共享鎖數量上則採用了往右移16位的形式,最高位都補0,
即11111111111111111111111111111111變成了類似00000000000000001111111111111111的形式獲取高16位

寫鎖的獲取 

一、
    c!=0說明有鎖了,
    1.w==0成立說明有多個讀鎖在用了,這時不能獲取寫鎖,因為寫鎖將導致資料亂掉
    2.w!=0 成立 說明當前有寫鎖,如果該執行緒進來競爭寫鎖又不是當前執行緒的話則不行,他們是獨佔的,同時如果寫鎖進來後加起來超過了65535(即16位都為1的情況下再進來一個寫鎖的話)則報錯,超出了能記錄的鎖數量限制

二、
    c==0說明沒有任何的鎖,

   前面判斷寫是不是需要阻塞(這個是公平鎖和非公平鎖的阻塞情況),
  下面列的是公平鎖的,非公平鎖就直接返回了false

如果AQS佇列不為空或者當前執行緒不在佇列頭部那麼就阻塞佇列(即加入到同步佇列中自身不斷自旋)
後面判斷獲取寫鎖,失敗直接false,其他則true

寫鎖的釋放

判斷是否存在重入的情況,如果是重入的話不能將當前的執行緒資訊清空

讀鎖的獲取
1.首先判斷是否存在寫鎖 同時當前執行緒是否為那個執行緒,如果不是拜拜,如果是的話則到2(允許寫鎖的執行緒同時獲取讀鎖)
2.讀是否阻塞,讀鎖數量有沒有超過,那麼讀鎖就CAS +1,如果判斷沒通過自旋去吧

讀鎖釋放
不斷的自旋CAS

讀鎖裡面存在一個特別的HoldCounter,因為讀鎖是共享的,存在多個讀鎖的情況,同時釋放的時候其實是類似計數器的形式,為了避免一個執行緒操作了一個不屬於自己監聽器物件的異常,也就是一個執行緒釋放了一個不屬於自己的或不存在的共享鎖就引入了HoldCounter
HoldCounter的作用後我們就可以猜到它的作用其實就是當前執行緒持有共享鎖(讀取鎖)的數量,包括重入的數量。那麼這個數量就必須和執行緒繫結在一起,表示執行緒持有讀取鎖數量的計數器。可以看到這裡使用ThreadLocal將HoldCounter繫結到當前執行緒上,同時HoldCounter也持有執行緒Id,這樣在釋放鎖的時候才能知道ReadWriteLock裡面快取的上一個讀取執行緒(cachedHoldCounter)是否是當前執行緒。這樣做的好處是可以減少ThreadLocal.get()的次數,因為這也是一個耗時操作。需要說明的是這樣HoldCounter繫結執行緒id而不繫結執行緒物件的原因是避免HoldCounter和ThreadLocal互相繫結而GC難以釋放它們(儘管GC能夠智慧的發現這種引用而回收它們,但是這需要一定的代價),所以其實這樣做只是為了幫助GC快速回收物件而已。


相關文章