說到 ReentrantReadWriteLock,首先要做的是與 ReentrantLock 劃清界限。它和後者都是單獨的實作,彼此之間沒有繼承或實作的關係。然後就是總結這個鎖機制的特性了:
WriteLock 可以獲取 ReadLock,但是反過來 ReadLock 想要獲得 WriteLock 則永遠都不要想。WriteLock 可以降級為 ReadLock,順序是:先獲得 WriteLock 再獲得 ReadLock,然後釋放 WriteLock,這時候執行緒將保持 ReadLock 的持有。反過來 ReadLock 想要升級為 WriteLock 則不可能,為什麼?參看 (a)。ReadLock 可以被多個執行緒持有並且在作用時排斥任何的 WriteLock,而 WriteLock 則是完全的互斥。這一特性最為重要,因為對於高讀取頻率而相對較低寫入的資料結構,使用此類鎖同步機制則可以提高併發量。ReadLock 還是 WriteLock 都支援 Interrupt,語義與 ReentrantLock 一致。WriteLock 支援 Condition 並且與 ReentrantLock 語義一致,而 ReadLock 則不能使用 Condition,否則拋出 UnsupportedOperationException 異常。以上就是比較重要的,或者衡量是否使用 ReentrantReadWriteLock 的基礎了。下面還是寫個小範例說明部分內容:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockSample {
public static void main(String[] args) {
testReadLock();
// testWriteLock();
}
public static void testReadLock() {
final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport();
support.initCache();
Runnable runnable = new Runnable() {
public void run() {
support.get("test");
}
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(new Runnable() {
public void run() {
support.put("test", "test");
}
}).start();
}
public static void testWriteLock() {
final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport();
support.initCache();
new Thread(new Runnable() {
public void run() {
support.put("key1", "value1");
}
}).start();
new Thread(new Runnable() {
public void run() {
support.put("key2", "value2");
}
}).start();
new Thread(new Runnable() {
public void run() {
support.get("key1");
}
}).start();
}
}
class ReadWriteLockSampleSupport {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private volatile boolean completed;
private Map<String,String> cache;
public void initCache() {
readLock.lock();
if(!completed) {
// Must release read lock before acquiring write lock
readLock.unlock(); // (1)
writeLock.lock(); // (2)
if(!completed) {
cache = new HashMap<String,String>(32);
completed = true;
}
// Downgrade by acquiring read lock before releasing write lock
readLock.lock(); // (3)
writeLock.unlock(); // (4) Unlock write, still hold read
}
System.out.println("empty? " + cache.isEmpty());
readLock.unlock();
}
public String get(String key) {
readLock.lock();
System.out.println(Thread.currentThread().getName() + " read.");
startTheCountdown();
try{
return cache.get(key);
}
finally{
readLock.unlock();
}
}
public String put(String key, String value) {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + " write.");
startTheCountdown();
try{
return cache.put(key, value);
}
finally {
writeLock.unlock();
}
}
/** A simple countdown, it will stop after about 5s. */
public void startTheCountdown() {
long currentTime = System.currentTimeMillis();
for(;;) {
long diff = System.currentTimeMillis() - currentTime;
if(diff > 5000) {
break;
}
}
}
}
這個範例改造自 JDK 的 API 提供的示例,其中 ReadWriteLockSampleSupport 輔助類別負責維護一個 Map,當然前提是這個 Map 大部分的多執行緒下都是讀取,只有很少的比例是多執行緒競爭修改 Map 的值。其中的 initCache() 簡單的說明了特性 (a)、(b)。在這個方法中如果把註釋 (1) 和 (2) 處的程式碼調換位置,就會發現輕而易舉的死鎖了,當然是因為特性 (a) 的作用了。而註釋 (3)、(4) 處的程式碼位置則再次證明了特性 (a),並且有力的反映了特性 (b)——WriteLock 在 cache 初始化完畢之後,降級為 ReadLock。另外 get()、put() 方法在執行緒獲取鎖之後會在方法中停留近 5 秒的時間。
ReentrantReadWriteLockSample 中的兩個靜態測試方法則分別測試了 ReadLock 和 WriteLock 的排斥性。testReadLock() 中,開啟三個執行緒,前兩者試圖獲取 ReadLock 而後者去獲取 WriteLock。執行結果可以看到:ReadWriteLockSampleSupport 的 get() 方法中的列印結果在前兩個執行緒中幾乎同時顯示,而 put() 中的列印結果則要等上近 5 秒。這就說明了,ReadLock 可以多執行緒持有並且排斥 WriteLock 的持有執行緒。testWriteLock() 中,也開啟三個執行緒。前兩個是去獲取 WriteLock,最後一個獲取 ReadLock。執行的結果是三個列印結果都有近 5 秒的間隔時間,這說明了 WriteLock 是獨佔的,比較獨!
這篇 ReentrantReadWriteLock 的總結寫的有點遲了,主要是最近對 js 和 ajax 很有興趣,突然覺得 css 也很好玩。看著網上很多人對技術的狂熱和個人規劃,我想對我而言:不迷戀技術而是作為興趣,不管是 J2EE 還是 Web 前端,不管是移動設備的三方開發還是專業的視頻剪輯技術,我都希望很自然地感興趣了,有條件了就去狠狠地玩玩。我想我迷戀的只是高效能的計算機和互聯網,哈哈。