在之前提到的synchronized的互斥鎖和ReentrantLock都屬於獨佔鎖定,這些鎖在同一時刻只能允許一個線程進行訪問。而讀寫鎖允許同一時刻有多個讀線程進行訪問,但是在有寫線程的時候,所有的讀線程和其他所有的寫線程都將阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,這種分離提高了並發性,因為在使用獨佔鎖定的時候,讀讀線程也是被阻塞的,相比之下確實提高了並行度。
讀寫鎖除了保證寫操作對讀操作的可見度和讀操作的並行度的提升外,還能夠簡化讀寫互動的編程情境。試想在一個讀多寫少的情境下,如果沒有讀寫鎖,正常的互動機制就是等待/通知機制了,就是在寫操作開始時,所有晚於寫操作的讀操作都會陷入阻塞狀態,只有當寫操作完成操作並進行通知之後,讀操作才能得到執行的機會。使用讀寫鎖則可以大大簡化編程難度,在讀操作時擷取讀鎖,因為讀鎖可以被多個讀線程擷取,所以提高了並行度,寫操作時擷取寫鎖即可。讀寫所使用與讀大於寫的情境,Java中讀寫鎖的重要實現是ReentrantReadWriteLock。ReentrantReadWriteLock的上層介面是ReadWriteLock。ReadWriteLock介面只有兩個方法:readLock()和writeLock()。
下面通過一個例子示範ReentrantReadWriteLock的使用:代碼實現了一個緩衝樣本
package com.rhwayfun.concurrency.r0405;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantReadWriteLock;/** * Created by rhwayfun on 16-4-5. */public class Cache { static Map<String,Object> map = new HashMap<String, Object>(); static ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); static Lock readLock = rw.readLock(); static Lock writeLock = rw.writeLock(); static DateFormat format = new SimpleDateFormat("HH:mm:ss"); /** * 擷取一個key對應的value * @param key * @return */ public static final Object get(String key){ readLock.lock(); try { return map.get(key); }finally { readLock.unlock(); } } /** * 設定key對應的value,並返回舊的value * @param key * @param value */ public static final Object put(String key,Object value){ writeLock.lock(); try { return map.put(key,value); }finally { writeLock.unlock(); } } /** * 清空所有的內容 */ public static final void clear(){ writeLock.lock(); try { map.clear(); }finally { writeLock.unlock(); } } /** * 寫線程 */ static class Writer implements Runnable{ private Cache cache; public Writer(Cache cache) { this.cache = cache; } public void run() { long start = System.currentTimeMillis(); int i = 0; for (;;i++){ if (System.currentTimeMillis() - start > 1000 * 5){ break; } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } String value = format.format(new Date()); cache.put(String.valueOf(i),value); System.out.println(Thread.currentThread().getName() + ":is writing data " + String.valueOf(i) + "=" + value + " at " + format.format(new Date())); } System.out.println(Thread.currentThread().getName() + ":finish writing data at " + format.format(new Date())); } } /** * 讀線程 */ static class Reader implements Runnable{ private Cache cache; public Reader(Cache cache) { this.cache = cache; } public void run() { int i = 0; for (;;i++){ Object obj = cache.get(String.valueOf(i)); if (obj instanceof String){ System.out.println(Thread.currentThread().getName() + ":is reading data " + String.valueOf(i) + "=" + obj +" at " + format.format(new Date())); } } } } public static void main(String[] args) throws InterruptedException { Cache cache = new Cache(); new Thread(new Writer(cache),"Writer").start(); TimeUnit.SECONDS.sleep(5); for (int i = 0; i < 5; i++){ new Thread(new Reader(cache),"Reader" + i).start(); TimeUnit.SECONDS.sleep(1); } }}
程式啟動並執行結果如下:
上述樣本中Cache組合使用一個非安全執行緒的HashMap作為緩衝的實現,同時使用讀鎖和寫鎖來保證Cache是安全執行緒的。在讀操作get方法中使用讀鎖,使得並發訪問該方法的時候不會阻塞。在寫操作put方法中使用寫鎖,在更新HashMap的時候必須提前擷取寫鎖,擷取寫鎖的線程將會阻塞讀鎖和寫鎖的擷取,而只有在寫鎖被釋放後其他讀寫操作才能繼續執行。