ReentrantReadWriteLock讀寫鎖的使用2
來源:程序員人生 發布時間:2015-02-03 08:40:39 閱讀次數:3288次
本文可作為傳智播客《張孝祥-Java多線程與并發庫高級利用》的學習筆記。
這1節我們做1個緩存系統。
在讀本節前
請先瀏覽
ReentrantReadWriteLock讀寫鎖的使用1
初版
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
public static void main(String[] args) {
CacheDemo cd = new CacheDemo();
System.out.println("ss "+cd.getData2("ss"));
System.out.println("ss "+cd.getData2("ss"));
System.out.println("mm "+cd.getData2("mm"));
System.out.println("mm "+cd.getData2("mm"));
}
public Object getData2(String key){
Object o=cache.get(key);
if (o==null) { //標識1
System.out.println("第1次查 沒有"+key);
o=Math.random(); //實際上從
數據庫中取得
cache.put(key, o);
}
return o;
}
}
運行結果:
第1次查 沒有ss
ss 0.4045014284225158
ss 0.4045014284225158
第1次查 沒有mm
mm 0.9994663041529088
mm 0.9994663041529088
似乎沒有問題。
你覺得呢?
如果有3個線程同時第1次到了getData2()的標識1處(所查找的key也都1樣),1檢查o為null,然后就去查
數據庫。在這類情況下,就等于3個線程查同1個key,然后都去了
數據庫。
這明顯是不公道的。
第2版 synchronized
最簡單的辦法
public synchronized Object getData2(String key){
//.....
}
第3版 鎖
上1節我們提到了ReentrantLock可以替換synchronized,前者是1種更加面向對象的設計。那我們試試。
private Lock l=new ReentrantLock(); //l作為CacheDemo的成員變量
public Object getData3(String key) {
l.lock();
Object o=null;
try {
o = cache.get(key);
if (o == null) { // 標識1
System.out.println("第1次查 沒有" + key);
o = Math.random(); // 實際上從
數據庫中取得
cache.put(key, o);
}
} finally {
l.unlock();
}
return o;
}
第3版可以嗎?
可以個p。
為何,自己想。
我們得使用ReentrantReadWriteLock,在同1個方法中既有讀鎖也有寫鎖。
首先我又1個問題:
對同1個線程,可以在加了讀鎖后,沒有解開讀鎖前,再加寫鎖嗎?
換句話說,下面的代碼會停嗎?
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
CacheDemo cd = new CacheDemo();
cd.getData2();
}
public void getData2(){
rwl.readLock().lock();
System.out.println(1);
rwl.writeLock().lock();
System.out.println(2);
rwl.readLock().unlock();
System.out.println(3);
rwl.writeLock().unlock();
System.out.println(4);
}
}
親身試1下,控制臺輸出1后,程序就不動了。
說明加寫鎖前,讀鎖得先解開。
第4版
即有讀鎖,又有寫鎖
public static void main(String[] args) {
CacheDemo cd = new CacheDemo();
System.out.println("ss "+cd.getData4("ss"));
System.out.println("ss "+cd.getData4("ss"));
System.out.println("mm "+cd.getData4("mm"));
System.out.println("mm "+cd.getData4("mm"));
}
public Object getData4(String key){
rwl.readLock().lock();
Object o=null;
try {
o=cache.get(key);
if (o==null) {
System.out.println("第1次查 沒有"+key);
rwl.readLock().unlock(); //標識4
rwl.writeLock().lock();
try {
if(value==null){ //標識0
value = "aaaa";//實際調用 queryDB();
cache.put(key,value);
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock(); //標識1
}
}finally {
rwl.readLock().unlock(); //標注2
}
return o;
}
結果
第1次查 沒有ss
ss 0.5989899889645358
ss 0.5989899889645358
第1次查 沒有mm
mm 0.8534424949014686
mm 0.8534424949014686
這個還有3個問題
1為何在標識2處還得解鎖。
這個答案在上1節已提過。
2為何在標識1出還得加鎖?
如果標識1處不加鎖,且程序1直正常履行,那末到標識2處,它解誰的鎖?
3為何標識0出還要檢查1次?
如果同時又3個線程運行到標識4(查找相同的key),其中1個取得寫鎖,寫入數據后,釋放了寫鎖。此時另外兩個線程還需要去
數據庫跑1趟么?
另外把標識1處的加讀鎖,放到finally的前面稱之為降級鎖。對降級鎖,我目前也不太清楚。
官方文檔中給了1個例子就是關于降級鎖,以下
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
感謝glt
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈