【Java多執行緒】讀寫鎖ReentrantReadWriteLock
1.讀寫鎖介紹
鎖(如Mutex和ReentrantLock)基本都是排他鎖,在同一時刻只能同一個執行緒訪問。而讀寫鎖在同時可以允許多個讀執行緒訪問,但是在寫執行緒訪問時,所有的讀執行緒和其他寫執行緒均被阻塞。
讀鎖不支援條件變數。
2.ReentrantReadWriteLock 特性
只能降級,不能升級
3.ReentrantReadWriteLock API
ReadWriteLock介面僅定義了獲取讀鎖和寫鎖的兩個方法,即readLock()方法和writeLock()方法。
而其實現——ReentrantReadWriteLock,除了介面方法之外,還提供了一些便於外界監控其內部工作狀態的方法,如圖。
4.讀寫鎖的實現分析
4.1 讀寫狀態的設計
4.1.1
讀寫鎖的自定義同步器需要在同步狀態(一個整型變數)上維護多個讀執行緒和一個寫執行緒的狀態。
讀寫鎖將變數切割成兩個部分,高16位表示讀,低16位表示寫
[上圖表明一個執行緒已經獲取了寫鎖,且重進入2次,同時也連續獲取了2次讀鎖。]
4.1.2 通過位運算快速確定讀寫狀態
假設當前同步狀態為S,
求寫狀態:S&0000FFFF,這樣相當於抹去高16位(任意數&0=0);
求讀狀態:S>>>16(右移16位,無符號前面補0)。
寫狀態增加1:S+1;
讀狀態增加1:S+(1<<16),也就是S+00010000。
根據狀態的劃分能得出一個推論:S不等於0時,當寫狀態(S&0x0000FFFF)等於0時,則讀狀態(S>>>16)大於0,即讀鎖已被獲取。
4.2 寫鎖的獲取與釋放
如果讀鎖已被獲取(讀狀態不為0)或者該執行緒不是已獲得寫鎖的執行緒,當前執行緒進入等待狀態;
如果當前執行緒已獲得寫鎖,則增加寫狀態。
[ReentrantReadWriteLock的tryAcquire方法 程式碼]
4.3 讀鎖的獲取與釋放
在沒有寫執行緒訪問(或寫狀態為0時),讀鎖總被成功獲取,所做的只是(執行緒安全地,依靠CAS保證)增加讀狀態;
如果已有寫鎖被其他執行緒獲取,進入等待狀態。
[ReentrantReadWriteLock的tryAcquireShared方法 程式碼]
讀鎖的每次釋放減少讀狀態(1<<16)
4.4 鎖降級
鎖降級:寫鎖降級為讀鎖。把持住當前擁有的寫鎖,再獲取到讀鎖,隨後釋放先前擁有的寫鎖。
5.讀寫鎖原理
讀寫鎖使用的是同一個Sync同步器,因此等待佇列、state等也是同一個。
5.1 加寫鎖
5.2 加讀鎖、寫鎖失敗
5.3 寫鎖釋放,喚醒連續讀鎖(直到遇見寫鎖)
這裡喚醒t2,t3。t4要寫鎖,所以不喚醒。
5.4 讀鎖釋放
釋放讀鎖t2,t3,喚醒寫鎖t4。