Java執行緒總結(八):併發包------讀寫鎖ReadWriteLock的簡單例子詳細理解
初次接觸ReadWriteLock類時也在網上查了很多資料,很容易瞭解到ReadWriteLock是讀寫鎖,並且讀寫鎖的機制有以下三個特點:
讀鎖---讀鎖 (不互斥)
讀鎖---寫鎖 (互斥)
寫鎖---寫鎖 (互斥)
什麼意思呢?
網上很多資料,直接用這三個特點實現一個快取的例子進行了講解,但是對小白來說還有那麼一絲絲的迷惑(老鳥忽略),下面就逐一演示:
1. 讀鎖---讀鎖
main方法裡兩個執行緒都在讀資料,在read()方法裡已經上了讀鎖且沒有解鎖(為了測試)
public class ReadWriteLockTest { public static void main(String[] args) { final ReadAndWrite raw = new ReadAndWrite(); raw.map.put("data", 1); //執行緒1,讀資料 new Thread( new Runnable() { @Override public void run() { while(true) raw.read(); } }).start(); //執行緒2,讀資料 new Thread( new Runnable() { @Override public void run() { while(true) raw.read(); } }).start(); } } class ReadAndWrite{ ReadWriteLock rwlock = new ReentrantReadWriteLock();//讀寫鎖 Map map = new HashMap();//共享的資料 public void read(){ rwlock.readLock().lock();//上讀鎖,且沒有解鎖 System.out.println(Thread.currentThread().getName()+"讀開始..."); System.out.println(Thread.currentThread().getName()+"讀資料為:"+map.get("data")); System.out.println(Thread.currentThread().getName()+"讀結束..."); try { Thread.sleep((long) (Math.random()*1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
(可以看到兩個執行緒都能不斷讀資料,記住這個現象,接著看下面的例子進行對比)
執行結果:
Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
Thread-1讀開始...
Thread-1讀資料為:1
Thread-1讀結束...
Thread-1讀開始...
Thread-1讀資料為:1
Thread-1讀結束...
Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
2. 讀鎖---寫鎖
main方法裡執行緒1在讀資料,執行緒2在寫資料,將read()方法裡的“解讀鎖”註釋掉和不註釋分別執行對比結果
public class ReadWriteLockTest2 { public static void main(String[] args) { final ReadAndWrite raw = new ReadAndWrite(); raw.map.put("data", 1); //執行緒1,讀資料 new Thread( new Runnable() { @Override public void run() { while(true) raw.read(); } }).start(); //執行緒2,寫資料 new Thread( new Runnable() { @Override public void run() { while(true) raw.write(); } }).start(); } } class ReadAndWrite{ ReadWriteLock rwlock = new ReentrantReadWriteLock();//讀寫鎖 Map map = new HashMap();//共享的資料 public void read(){ rwlock.readLock().lock();//上讀鎖 System.out.println(Thread.currentThread().getName()+"讀開始..."); System.out.println(Thread.currentThread().getName()+"讀資料為:"+map.get("data")); System.out.println(Thread.currentThread().getName()+"讀結束..."); <span style="color:#ff0000;">//rwlock.readLock().unlock();//解讀鎖</span> try { Thread.sleep((long) (Math.random()*1000)); } catch (InterruptedException e) { e.printStackTrace(); } } public void write(){ rwlock.writeLock().lock();//上寫鎖 System.out.println(Thread.currentThread().getName()+"寫開始..."); double data = Math.random(); map.put("data", data); System.out.println(Thread.currentThread().getName()+"寫資料為:"+data); System.out.println(Thread.currentThread().getName()+"寫結束..."); rwlock.writeLock().unlock();//解寫鎖 try { Thread.sleep((long) (Math.random()*1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
read()方法裡“解讀鎖”註釋掉,(可以看到只有執行緒1在讀資料,執行緒2是阻塞狀態無法寫資料,證明了讀鎖和寫鎖的互斥)
執行結果:
Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
read()方法裡“解讀鎖”不註釋,(可以看到執行緒1和執行緒2交替的讀和寫資料,也就是讀鎖解了,其他執行緒就可以寫資料了,寫鎖解了,其他執行緒就可以讀資料了)
執行結果:
Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
Thread-1寫開始...
Thread-1寫資料為:0.4387383401358649
Thread-1寫結束...
3. 寫鎖---寫鎖
相信看了上面兩個例子,基本就明白了寫鎖和寫鎖是怎麼互斥的,同樣執行緒1和執行緒2都進行寫資料,write()方法上寫鎖並且不解鎖(測試才這樣寫的)
public class ReadWriteLockTest3 {
public static void main(String[] args) {
final ReadAndWrite raw = new ReadAndWrite();
raw.map.put("data", 1);
//執行緒1,寫資料
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.write();
}
}).start();
//執行緒2,寫資料
new Thread(
new Runnable() {
@Override
public void run() {
while(true)
raw.write();
}
}).start();
}
}
class ReadAndWrite{
ReadWriteLock rwlock = new ReentrantReadWriteLock();//讀寫鎖
Map map = new HashMap();//共享的資料
public void write(){
rwlock.writeLock().lock();//上寫鎖
System.out.println(Thread.currentThread().getName()+"寫開始...");
double data = Math.random();
map.put("data", data);
System.out.println(Thread.currentThread().getName()+"寫資料為:"+data);
System.out.println(Thread.currentThread().getName()+"寫結束...");
try {
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(write()方法裡上了寫鎖並且沒有解鎖,所以只有一個執行緒在寫資料,其他執行緒無法進行寫資料,你也可以把解鎖給加上,執行一下對比看看)執行結果:
Thread-0寫開始...
Thread-0寫資料為:0.2439106501627466
Thread-0寫結束...
Thread-0寫開始...
Thread-0寫資料為:0.44688857423885386
Thread-0寫結束...
Thread-0寫開始...
相信通過以上的程式碼和執行結果進行對比和觀察,應該已經理解了ReadWriteLock三個特點,下面貼上實現快取類的程式碼(jdk中ReentrantReadWriteLock類的一個例子):
public class Cache {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public Object getData(String key) {
// 首先上讀鎖
rwLock.readLock().lock();
// 首先從快取中獲取
Object value = null;
try {
Thread.sleep(1000);
value = cache.get(key);
if (value == null) {
// 如果快取中沒有資料,那麼就從資料庫中獲取
// 但此時需要上寫鎖,只需要讓一個程序進行寫資料
// 首先去除讀鎖,然後加上寫鎖
rwLock.readLock().unlock();
rwLock.writeLock().lock();
try {
// 注意防止多執行緒執行到上一步,某個執行緒寫完資料後
// 別的執行緒就需要看是否有資料再決定是否進行寫操作
// 在寫之前再讀一次,防止最開始的執行緒都進行寫操作</span>
value = cache.get(key);
// 第一個執行緒寫完後,防止後面的執行緒再次寫資料
if (value == null) {
System.out.println("有執行緒寫資料........");
value = "資料庫中獲取";
// 將資料放入快取
cache.put(key, value);
System.out.println("資料寫完了.......");
}
} finally {
rwLock.readLock().lock();// 恢復讀鎖,鎖的重入
rwLock.writeLock().unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();// 解讀鎖
}
return value;
}
}