個人博客請訪問 http://www.x0100.top
概念
顧名思義,ReentrantReadWriteLock名爲“可重入讀寫鎖”,它維護兩個鎖:讀鎖和寫鎖。在沒有寫鎖的情況下,讀鎖允許多個線程同時訪問,而寫鎖是獨佔的,
ReentrantReadWriteLock主要特性有以下幾個:
-
公平性。支持公平鎖和非公平鎖,默認是非公平鎖。非公平鎖比公平鎖有更高的吞吐量。
-
可重入。允許讀鎖可寫鎖可重入。寫鎖可以獲得讀鎖,讀鎖不能獲得寫鎖。讀寫鎖最多支持65535個遞歸寫入鎖和65535個遞歸讀取鎖(這裏有個需要注意的地方,ReentrantLock的可重入次數爲2^32-1,而ReentrantReadWriteLock可重入次數爲2^16-1=65535,爲啥呢?這是因爲ReentrantReadWriteLock將AQS中的state域分成了兩部分,讀鎖和寫鎖各佔16位,具體可以往下看)。
-
鎖降級。在獲得寫鎖的情況下再獲得讀鎖(這和寫鎖是獨佔的並不衝突,這裏是指一個線程可以活得寫鎖再獲得讀鎖,獨佔是指多個線程之間),然後釋放寫鎖稱爲鎖降級,反之稱爲鎖升級。允許寫鎖降低爲讀鎖,反之不允許。
使用
ReentrantReadWriteLock可以用來提高某些集合的併發性能。當集合比較大,並且讀比寫頻繁時,可以使用該類。
讀寫鎖的簡單示例如下:
class Cache{
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
static Lock r = rwLock.readLock();
static Lock w = rwLock.writeLock();
//獲取一個key對應的value
public static final Object get(String key){
r.lock();
try {
return map.get(key);
}finally {
r.unlock();
}
}
//設置key對應的value值
public static final Object put(String key, Object value){
w.lock();
try {
return map.put(key, value);
}finally {
w.unlock();
}
}
//清空所有內容
public static final void clear(){
w.lock();
try {
map.clear();
}finally {
w.unlock();
}
}
}
鎖降級的示例如下:
class LockDec{
static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
static Lock readLock = rwLock.readLock();
static Lock writeLock = rwLock.writeLock();
public volatile boolean update = false;
public void processData(){
readLock.lock();
if (!update){
//必須先釋放讀鎖
readLock.unlock();
//鎖降級從寫鎖獲取到開始
writeLock.lock();
try {
if (!update){
//準備數據的流程
update = true;
}
readLock.lock();
}finally {
writeLock.unlock();
}
//鎖降級完成,寫鎖降級爲讀鎖
}
try {
//使用數據的流程
}finally {
readLock.unlock();
}
}
}
當數據發生變更後 ,update變量被設置爲true,此時所有訪問processData()方法的線程都能感知到變化。
基本原理
ReentrantReadWriteLock底層也是使用AQS實現的,但是AQS中只有一個state域,這樣就有了幾個問題:
-
AQS只有一個狀態,那麼如何表示 多個讀鎖 與 單個寫鎖 呢?
-
ReentrantLock 裏,狀態值表示重入計數,現在如何在AQS裏表示每個讀鎖、寫鎖的重入次數呢?
-
如何實現讀鎖、寫鎖的公平性呢?
ReentrantReadWriteLock的解決辦法總結起來有以下幾個:
-
將state域按位分成兩部分,高位部分表示讀鎖,低位表示寫鎖,由於寫鎖只有一個,所以寫鎖的重入計數也解決了,這也會導致寫鎖可重入的次數減小(前面提到的2^16-1)。
-
讀鎖是共享鎖,可以同時有多個,那麼只靠一個state來計算鎖的重入次數是不行的。ReentrantReadWriteLock是通過一個HoldCounter的類來實現的,這個類中有一個count計數器,同時該類通過ThreadLocal關鍵字被修飾爲線程私有變量,那麼每個線程都保留一份對讀鎖的重入次數。