ReadLock和WriteLock(讀寫鎖)
ReadWriteLock也是一個介面,提供了readLock和writeLock兩種鎖的操作機制,一個資源可以被多個執行緒同時讀,或者被一個執行緒寫,但是不能同時存在讀和寫執行緒。
使用場合
假設在程式中定義一個共享的資料結構用作快取,它大部分時間提供讀服務(例如:查詢和搜尋),而寫操作佔有的時間很少,但是寫操作完成之後的更新需要對後續的讀服務可見。
在沒有讀寫鎖支援的(Java 5 之前)時候,如果需要完成上述工作就要使用Java的等待通知機制,就是當寫操作開始時,所有晚於寫操作的讀操作均會進入等待狀態,只有寫操作完成並進行 通知之後,所有等待的讀操作才能繼續執行(寫操作之間依靠synchronized關鍵字進行同步),這樣做的目的是使讀操作都能讀取到正確的資料,而不會出現髒讀。改用讀寫鎖實現上述功能,只需要在讀操作時獲取讀鎖,而寫操作時獲取寫鎖即可,當寫鎖被獲取到時,後續(非當前寫操作執行緒)的讀寫操作都會被 阻塞,寫鎖釋放之後,所有操作繼續執行,程式設計方式相對於使用等待通知機制的實現方式而言,變得簡單明瞭。
特性
ReentrantReadWriteLock的實現裡面有以下幾個特性
**1、公平性:非公平鎖(預設)。**讀執行緒之間沒有鎖操作,所以讀操作沒有公平性和非公平性。寫操作時,由於寫操作可能立即獲取到鎖,所以會推遲一個或多個讀操作或者寫操作。非公平鎖的吞吐量要高於公平鎖。(公平鎖概念:公平鎖利用AQS的CLH佇列,釋放當前保持的鎖時,優先為等待時間最長的那個寫操作分配寫入鎖)
**2、重入性:**讀寫鎖允許讀執行緒和寫執行緒按照請求鎖的順序重新獲取讀取鎖或者寫入鎖。只有寫執行緒釋放了鎖,讀執行緒才可以獲取重入鎖,寫執行緒獲取寫入鎖後可以再次獲取讀取鎖,但是讀執行緒獲取讀取鎖後卻不能獲取寫入鎖。
**3、鎖降級:**寫執行緒獲取寫入鎖後可以獲取讀取鎖,然後釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖,從而實現鎖降級特性,經典cache案例使用了鎖降級
**4、鎖升級:**讀取鎖是不能直接升級為寫入鎖的。因此獲取一個寫入鎖需要先釋放所有的讀取鎖,如果有兩個讀取鎖試圖獲取寫入鎖,且都不釋放讀取鎖時,就會發生死鎖
**5、鎖獲取中斷:**讀取鎖和寫入鎖都支援獲取鎖期間被中斷
**6、條件變數:**寫入鎖提供了條件變數的支援,但是讀取鎖卻不允許獲取條件變數,否則會得到一個UnsupportedOperationExcetpion異常
**7、重入鎖:**讀取鎖和寫入鎖的數量最大分別只能是65535
讀寫鎖機制
讀-讀不互斥
讀-寫互斥
寫-寫互斥
示例程式碼:
import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockTest { private double data = 0; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public void get(){ try { rwl.readLock().lock(); System.out.println("----Thread:"+Thread.currentThread().getName()+"----read first value:"+data); Thread.sleep(1000); System.out.println("----Thread:"+Thread.currentThread().getName()+"----read second value:"+data); rwl.readLock().unlock(); } catch (Exception e) { // TODO: handle exception } } public void put(){ try { rwl.writeLock().lock(); data = Math.random(); System.out.println("----Thread:"+Thread.currentThread().getName()+"----write first value:"+data); Thread.sleep(100); rwl.writeLock().unlock(); } catch (Exception e) { // TODO: handle exception } } }
public class MainReadWritLockTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final ReadWriteLockTest rwlt = new ReadWriteLockTest();
for(int i=0; i<5;i++){
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
rwlt.get();
}
}).start();
}
for(int i=0; i<2;i++){
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
rwlt.put();
}
}).start();
}
}
}
執行程式碼,我們可以很明顯觀察到,5個執行緒讀取時,沒有互斥。讀完後寫執行緒才開始執行,說明讀讀不互斥,讀寫有互斥。
執行結果:
鎖升級降級示例
讀寫鎖經典案例,防快取系統
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Map<String,Object> cache = new HashMap<String,Object>();
public Object get(String key){
Object value = null;
try {
rwl.readLock().lock();
value = cache.get(key);
if(null==value){
rwl.readLock().unlock();//釋放都鎖,獲取寫鎖
try {
rwl.writeLock().lock();
//獲取寫鎖後再次判斷物件是否為null,方式下一個等待的寫執行緒進入後直接獲取資料去
value = cache.get(key);
if(null==value){
System.out.println(Thread.currentThread().getName());
value="aaaaa";//實際操作程式碼從資料庫中查詢得到的物件
cache.put(key, value);
}
//自身鎖降級為都鎖
rwl.readLock().lock();
} catch (Exception e) {
// TODO: handle exception
}finally{
rwl.writeLock().unlock();//釋放寫鎖
}
}
} catch (Exception e) {
// TODO: handle exception
}finally{
rwl.readLock().unlock();
}
return value;
}
}
import java.util.HashMap;
import java.util.Map;
public class MainReadWritLockTest {
Map<String,Object> cache = new HashMap<String,Object>();
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final ReadWriteLockTest rwlt = new ReadWriteLockTest();
for(int i=0; i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Object value= rwlt.get("id");
System.out.println(Thread.currentThread().getName()+"--"+value);
}
}).start();
}
}
}
上述示例中,cache組合了一個非執行緒安全的HashMap作為快取的實現,同時使用讀寫鎖的讀鎖和寫鎖來保證cache是執行緒安全的。在讀操作 get(String key)方法中,需要獲取讀鎖,這使得併發訪問該方法時不會被阻塞。寫操作put(String key, Object value)方法,在更新HashMap時必須提前獲取寫鎖,當寫鎖被獲取後,其他執行緒對於讀鎖和寫鎖的獲取均被阻塞,而只有寫鎖被釋放 之後,其他讀寫操作才能繼續。