1. 程式人生 > >多執行緒之使用讀寫鎖ReentrantReadWriteLock實現快取系統

多執行緒之使用讀寫鎖ReentrantReadWriteLock實現快取系統

簡單地快取系統:當有執行緒來取資料時,如果該資料存在我的記憶體中,我就返回資料;如果不存在我的快取系統中,那麼就去查資料庫,返回資料的同時儲存在我的快取中。

其中涉及到讀寫問題:當多個執行緒執行讀操作時(都加讀鎖),如果有資料返回;如果沒有資料時,則讓第一個讀的執行緒,進行獲取資料,然後進行寫操作,這時需要第一個執行緒先釋放掉讀鎖然後加寫鎖。第一個寫完後,在家讀鎖,其他執行緒使用時判斷,如果存在該資料,在直接過去讀取不用加寫鎖。

API上快取例子如下:

 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();
   }
 }
ReentrantReadWriteLock

此類具有以下屬性:

  • 獲取順序

    此類不會將讀取者優先或寫入者優先強加給鎖訪問的排序。但是,它確實支援可選的公平 策略。

    非公平模式(預設)
    當非公平地(預設)構造時,未指定進入讀寫鎖的順序,受到 reentrancy 約束的限制。連續競爭的非公平鎖可能無限期地推遲一個或多個 reader 或 writer 執行緒,但吞吐量通常要高於公平鎖。
    公平模式
    當公平地構造執行緒時,執行緒利用一個近似到達順序的策略來爭奪進入。當釋放當前保持的鎖時,可以為等待時間最長的單個 writer 執行緒分配寫入鎖,如果有一組等待時間大於所有正在等待的 writer 執行緒 的 reader 執行緒,將為該組分配寫入鎖。

    如果保持寫入鎖,或者有一個等待的 writer 執行緒,則試圖獲得公平讀取鎖(非重入地)的執行緒將會阻塞。直到當前最舊的等待 writer 執行緒已獲得並釋放了寫入鎖之後,該執行緒才會獲得讀取鎖。當然,如果等待 writer 放棄其等待,而保留一個或更多 reader 執行緒為佇列中帶有寫入鎖自由的時間最長的 waiter,則將為那些 reader 分配讀取鎖。

  • 重入

    此鎖允許 reader 和 writer 按照 ReentrantLock 的樣式重新獲取讀取鎖或寫入鎖。在寫入執行緒保持的所有寫入鎖都已經釋放後,才允許重入 reader 使用它們。

    此外,writer 可以獲取讀取鎖,但反過來則不成立。在其他應用程式中,當在呼叫或回撥那些在讀取鎖狀態下執行讀取操作的方法期間保持寫入鎖時,重入很有用。如果 reader 試圖獲取寫入鎖,那麼將永遠不會獲得成功。

  • 鎖降級

    重入還允許從寫入鎖降級為讀取鎖,其實現方式是:先獲取寫入鎖,然後獲取讀取鎖,最後釋放寫入鎖。但是,從讀取鎖升級到寫入鎖是不可能的

  • 鎖獲取的中斷

    讀取鎖和寫入鎖都支援鎖獲取期間的中斷。

  • 監測

    此類支援一些確定是保持鎖還是爭用鎖的方法。這些方法設計用於監視系統狀態,而不是同步控制。


         java實現如下:

package andy.thread.test;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author Zhang,Tianyou
 * @version 2014年11月9日 上午9:29:42
 */

public class ThreadCaChe {

	private static Map<String, Object> cacheMap = new HashMap<String, Object>();

	public static void main(String[] args) {

		for (int i = 0; i < 10; i++) {

			new Thread(new Runnable() {

				@Override
				public void run() {
					
					String obj = (String) getData("andy");
					System.out.println(obj);

				}
			}).start();

		}

	}


	public static Object getData(String key) {
		
		ReadWriteLock rwlLock = new ReentrantReadWriteLock();
		
		// 先加讀鎖
		rwlLock.readLock().lock();
		Object value = null;
		try {
			value = cacheMap.get(key);
			// 若不存在cache中
			if (value == null) {
				// 若果value為空 則釋放掉讀鎖,讓該執行緒獲取寫鎖,而其他執行緒只能等待該寫鎖釋放,才能在進讀鎖
				rwlLock.readLock().unlock();
				// 加寫鎖
				rwlLock.writeLock().lock();

				try {
					if (value == null) {
						// 從資料中獲取資料
						value = "andy is shuai ge";// 查詢資料庫
						// 存入快取中
						cacheMap.put(key, value);
					}
				} finally {
					rwlLock.writeLock().unlock();
				}

				rwlLock.readLock().lock();
			}

		} finally {
			// 釋放第一次獲取的讀鎖
			rwlLock.readLock().unlock();
		}

		return value;
	}
}

執行效果如下:

andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge
andy is shuai ge