多執行緒與併發----讀寫鎖

pengzhisen123發表於2018-05-12

    1、鎖分為讀鎖和寫鎖

    2、多個讀鎖不互斥

    3、讀鎖和寫鎖互斥

    4、寫鎖與寫鎖互斥

    總之,這是由JVM自己控制的,如果你的程式碼只讀取資料,可以多人同時讀,但不能同時寫,那就上讀鎖;若歌你的程式碼修改資料,只能有一個人在寫,且不能同時讀取,那就上寫鎖。


三個執行緒讀資料,三個執行緒寫資料示例:

        功能:可以同時讀,讀的時候不能寫,不能同時寫,寫的時候不能讀,讀的時候上讀鎖,讀完解鎖;寫的時候上寫鎖,寫完解鎖。注意finally解鎖

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {

	/**讀寫所使用
	* 三個執行緒讀,三個執行緒寫
	*/
	public static void main(String[] args) {
		//共享物件
		final Source source = new Source();
		//建立執行緒
		for (int i=0; i<3; i++){
			//讀
			new Thread(new Runnable(){
				public void run(){
					while (true)
						source.get();
				}
			}).start();
			//寫
			new Thread(new Runnable(){
				public void run(){
					while (true)
						source.put(new Random().nextInt(999));
				}
			}).start();
		}
	}
	static class Source
	{
		//共享資料
		private int data = 0;
		//要操作同一把鎖上的讀或寫鎖
		ReadWriteLock rwl = new ReentrantReadWriteLock();
		
		//讀方法
		public void get()
		{
			//上讀鎖
			rwl.readLock().lock();
			try
			{
				//獲取資料並輸出
				System.out.println("讀——"+Thread.currentThread().getName()+"正在獲取資料。。。");
				try
				{
					Thread.sleep(new Random().nextInt(6)*1000);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
				System.out.println("讀——"+Thread.currentThread().getName()+"獲取到的資料:"+data);
			}finally
			{
				//解鎖
				rwl.readLock().unlock();
			}			
		}
		//寫方法
		public void put(int data)
		{
			//上寫鎖
			rwl.writeLock().lock();
			try
			{
				//提示資訊
				System.out.println("寫——"+Thread.currentThread().getName()+"正在改寫資料。。。");
				try
				{
					Thread.sleep(new Random().nextInt(6)*1000);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
				this.data = data;
				System.out.println("寫——"+Thread.currentThread().getName()+"已將資料改寫為:"+data);
			}finally
			{
				//解鎖
				rwl.writeLock().unlock();
			}			
		}
	}
}

    執行結果為:

讀——Thread-0正在獲取資料。。。
讀——Thread-2正在獲取資料。。。
讀——Thread-4正在獲取資料。。。
讀——Thread-0獲取到的資料:0
讀——Thread-4獲取到的資料:0
讀——Thread-2獲取到的資料:0
寫——Thread-3正在改寫資料。。。
寫——Thread-3已將資料改寫為:565
寫——Thread-3正在改寫資料。。。


ReentrantReadWriteLock

構造方法摘要

ReentrantReadWriteLock()           使用預設(非公平)的排序屬性建立一個新的 ReentrantReadWriteLock

ReentrantReadWriteLock(boolean fair)           使用給定的公平策略建立一個新的 ReentrantReadWriteLock

 

方法摘要

protected  Thread

getOwner()           返回當前擁有寫入鎖的執行緒,如果沒有這樣的執行緒,則返回 null

protected  Collection<Thread>

getQueuedReaderThreads()           返回一個 collection,它包含可能正在等待獲取讀取鎖的執行緒。

protected  Collection<Thread>

getQueuedThreads()           返回一個 collection,它包含可能正在等待獲取讀取或寫入鎖的執行緒。

protected  Collection<Thread>

getQueuedWriterThreads()           返回一個 collection,它包含可能正在等待獲取寫入鎖的執行緒。

 int

getQueueLength()           返回等待獲取讀取或寫入鎖的執行緒估計數目。

 int

getReadHoldCount()           查詢當前執行緒在此鎖上保持的重入讀取鎖數量。

 int

getReadLockCount()           查詢為此鎖保持的讀取鎖數量。

protected  Collection<Thread>

getWaitingThreads(Condition condition)           返回一個 collection,它包含可能正在等待與寫入鎖相關的給定條件的那些執行緒。

 int

getWaitQueueLength(Condition condition)           返回正等待與寫入鎖相關的給定條件的執行緒估計數目。

 int

getWriteHoldCount()           查詢當前執行緒在此鎖上保持的重入寫入鎖數量。

 boolean

hasQueuedThread(Thread thread)           查詢是否給定執行緒正在等待獲取讀取或寫入鎖。

 boolean

hasQueuedThreads()           查詢是否所有的執行緒正在等待獲取讀取或寫入鎖。

 boolean

hasWaiters(Condition condition)           查詢是否有些執行緒正在等待與寫入鎖有關的給定條件。

 boolean

isFair()           如果此鎖將公平性設定為 ture,則返回 true

 boolean

isWriteLocked()           查詢是否某個執行緒保持了寫入鎖。

 boolean

isWriteLockedByCurrentThread()           查詢當前執行緒是否保持了寫入鎖。

 ReentrantReadWriteLock.ReadLock

readLock()           返回用於讀取操作的鎖。

 String

toString()           返回標識此鎖及其鎖狀態的字串。

 ReentrantReadWriteLock.WriteLock

writeLock()           返回用於寫入操作的鎖。

JDK幫助文件中的示例用法。下面的程式碼展示瞭如何利用重入來執行升級快取後的鎖降級(為簡單起見,省略了異常處理): 

class CachedData {

   Object data;

   volatile boolean cacheValid;	資料有沒有標記

   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();上寫鎖

        // 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();

        rwl.writeLock().unlock(); // Unlock write, still hold read

     }

 

     use(data);存在直接使用資料

     rwl.readLock().unlock();解除讀鎖

   }

 }


面試題:設計一個快取系統

快取系統:你要取資料,需呼叫我的public Object getData(String key)方法,我要檢查我內部有沒有這個資料,如果有就直接返回,如果沒有,就從資料庫中查詢這個數,查到後將這個資料存入我內部的儲存器中,下次再有人來要這個資料,我就直接返回這個數不用再到資料庫中找了。 你要取資料不要找資料庫,來找我。

class CachedSystem
{	
	//快取系統的儲存器
	private Map<String, Object> cache = new HashMap<String, Object>();
	//取資料方法可能有多個執行緒來取資料,沒有資料的話又會去資料庫查詢,需要互斥
	
	public synchronized Object get(String key)
	{	
		//先查詢內部儲存器中有沒有要的值
	
		Object value = cache.get(key);
		if (value==null)//如果沒有,就去資料庫中查詢,並將查到的結果存入內部儲存器中
		{	
			value = “aaaa”; //實際程式碼是查詢後的結果 queryDB(key)	
			cache.put(key, value);	
		}	
		return value;
	}
	
	//上面的程式碼每次只能有一個執行緒來查詢,但只有寫的時候才需要互斥,修改如下
	
	//來一個讀寫鎖	
	ReadWriteLock rwl = new ReentrantReadWriteLock();	
	public Object get(String key){
		//上讀鎖	
		rwl.readLock().lock();	
		//先查詢內部儲存器中有沒有要的值	
		Object value = cache.get(key);	
		if (value==null)//如果沒有,就去資料庫中查詢,並將查到的結果存入內部儲存器中	
		{	
			//釋放讀鎖  上寫鎖	
			rwl.readLock().unlock();	
			rwl.writeLock().lock();	
			if (value==null)再次進行判斷,防止多個寫執行緒堵在這個地方重複寫		
			{		
				value = “aaaa”;		
				cache.put(key, value);	
			}
		//設定完成 釋放寫鎖,恢復讀寫狀態
		
		rwl.readLock().lock();	
		rwl.writeLock().unlock();	
		}
	//釋放讀鎖
	rwl.readLock().unlock();
	return value;	
	//注意:try finally中unlock
	}
}

相關文章