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時必須提前獲取寫鎖,當寫鎖被獲取後,其他線程對於讀鎖和寫鎖的獲取均被阻塞,而只有寫鎖被釋放 之後,其他讀寫操作才能繼續。

發佈了45 篇原創文章 · 獲贊 22 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章