如ReentrantLock都是排他鎖,這些鎖在同一時刻只容許一個線程進行訪問,而讀寫鎖在同一時刻能夠容許多個讀線程訪問,可是在寫線程訪問時,全部的讀線程和其餘寫線程均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,經過分離讀鎖和寫鎖,使得併發性相比通常的排他鎖有了很大提高。java
在沒有讀寫鎖以前,只能經過Java的等待通知機制,就是當寫操做開始時,全部晚於寫操做的讀操做均會進入等待狀態,只有寫操做完成並進行通知以後,全部等待的讀操做才能繼續執行(寫操做之間依靠synchronized關鍵進行同步),這樣作的目的是使讀操做能讀取到正確的數據,不會出現髒讀。改用讀寫鎖實現上述功能,只須要在讀操做時獲取讀鎖,寫操做時獲取寫鎖便可。當寫鎖被獲取到時,後續(非當前寫 操做線程)的讀寫操做都會被阻塞,寫鎖釋放以後,全部操做繼續執行。安全
Java併發包提供讀寫鎖接口ReadWriteLock的實現是 ReentrantReadWriteLock。其具備的特性包括:併發
1. ReentrantReadWriteLock相關API:ReadWriteLock僅定義了獲取讀鎖和寫鎖的兩個方法,即readLock()方法和writeLock()方法,而其實現——ReentrantReadWriteLock,除了接口方法以外,還提供了一些便於外界監控其內部工做狀態的方法。如ui
(1)public int getReadLockCount():返回當前讀鎖被獲取的次數,但不是佔用該鎖的線程數,也就是說,一個線程若是n次獲取該鎖,該方法返回n,而不是1。spa
(2) public int getReadHoldCount():返回當前線程獲取讀鎖的次數。線程
(3) public boolean isWriteLocked():判斷寫鎖是否被獲取。設計
(4)public int getWriteHoldCount():獲取寫鎖被獲取的次數。code
2.讀寫鎖使用實例:接口
public class TestReadWriteLock { static ReadWriteLock lock=new ReentrantReadWriteLock(); static Lock read=lock.readLock(); static Lock write=lock.writeLock(); static HashMap<String, Object> map=new HashMap<String, Object>(); static Object get(String key){ read.lock(); try { return map.get(key); } finally { read.unlock(); } } static void put(String key,Object value){ write.lock(); try { map.put(key, value); } finally { write.unlock(); } } static void clear(){ write.lock(); try { map.clear(); } finally { write.unlock(); } } }
以一個非線程安全的HashMap實現了一個簡單的線程安全的HashMap,在讀操做get(String key)方法中,須要獲取讀鎖,這使 得併發訪問該方法時不會被阻塞。寫操做put(String key,Object value)方法和clear()方法,在更新HashMap時必須提早獲取寫鎖,當獲取寫鎖後,其餘線程對於讀鎖和寫鎖的獲取均被阻塞,而 只有寫鎖被釋放以後,其餘讀寫操做才能繼續。get
讀寫鎖一樣依賴自定義同步器來實現同步功能,而讀寫狀態就是其同步器的同步狀態。以ReentrantLock中自定義同步器的實現爲例,同步狀態表示鎖被一個線程重複獲取的次數,而讀寫鎖的自定義同步器須要在同步狀態(一個整型變量)上維護多個讀線程和一個寫線程的狀態,使得該狀態的設計成爲讀寫鎖實現的關鍵。
在一個整形變量中維護多個狀態,須要使用「按位切割方法」,在讀寫鎖中,咱們須要將一個整形變量表示爲讀和寫兩種狀態,因此會將整型變量分割爲兩部分,高16位表示讀狀態,低16爲表示寫狀態。
經過位運算即可以肯定讀寫狀態。假設當前同步狀態值爲S,寫狀態等於S&0x0000FFFF(將高16位所有抹去),讀狀態等於S>>>16(無符號補0右移 16位)。當寫狀態增長1時,等於S+1,當讀狀態增長1時,等於S+(1<<16),也就是 S+0x00010000。
寫鎖是一個支持重進入的排它鎖。若是當前線程已經獲取了寫鎖,則增長寫狀態。若是當前線程在獲取寫鎖時,讀鎖已經被獲取(讀狀態不爲0)或者該線程不是已經獲取寫鎖的線程, 則當前線程進入等待狀態,源碼以下:
final boolean tryWriteLock() { Thread current = Thread.currentThread(); int c = getState(); if (c != 0) { int w = exclusiveCount(c); if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w == MAX_COUNT) throw new Error("Maximum lock count exceeded"); } if (!compareAndSetState(c, c + 1)) return false; setExclusiveOwnerThread(current); return true; }
該方法除了重入條件(當前線程爲獲取了寫鎖的線程)以外,增長了一個讀鎖是否存在的判斷。若是存在讀鎖,則寫鎖不能被獲取,緣由在於:讀寫鎖要確保寫鎖的操做對讀鎖可見,若是容許讀鎖在已被獲取的狀況下對寫鎖的獲取,那麼正在運行的其餘讀線程就沒法感知到當前寫線程的操做。所以,只有等待其餘讀線程都釋放了讀鎖,寫鎖才能被當前線程獲取,而寫鎖一旦被獲取,則其餘讀寫線程的後續訪問均被阻塞。
寫鎖的釋放與ReentrantLock的釋放過程基本相似,每次釋放均減小寫狀態,當寫狀態爲0時表示寫鎖已被釋放,從而等待的讀寫線程可以繼續訪問讀寫鎖,同時前次寫線程的修改對後續讀寫線程可見。
讀鎖是一個支持重進入的共享鎖,它可以被多個線程同時獲取,在沒有其餘寫線程訪問 (或者寫狀態爲0)時,讀鎖總會被成功地獲取,而所作的也只是(線程安全的)增長讀狀態。若是當前線程已經獲取了讀鎖,則增長讀狀態。若是當前線程在獲取讀鎖時,寫鎖已被其餘線程 獲取,則進入等待狀態。源碼以下:
final boolean tryReadLock() { Thread current = Thread.currentThread(); for (;;) { int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return false; int r = sharedCount(c); if (r == MAX_COUNT) throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return true; } } }
讀狀態是全部線程獲取讀鎖次數的總和,而每一個線程各自獲取讀鎖的次數只能選擇保存在ThreadLocal中,由線程自身維護。
在tryAcquireShared(int unused)方法中,若是其餘線程已經獲取了寫鎖,則當前線程獲取讀鎖失敗,進入等待狀態。若是當前線程獲取了寫鎖或者寫鎖未被獲取,則當前線程(線程安全, 依靠CAS保證)增長讀狀態,成功獲取讀鎖。 讀鎖的每次釋放(線程安全的,可能有多個讀線程同時釋放讀鎖)均減小讀狀態,減小的值是(1<<16)。
鎖降級指的是寫鎖降級成爲讀鎖。若是當前線程擁有寫鎖,而後將其釋放,最後再獲取讀鎖,這種分段完成的過程不能稱之爲鎖降級。鎖降級是指把持住(當前擁有的)寫鎖,再獲取到 讀鎖,隨後釋放(先前擁有的)寫鎖的過程。
static Object putAndget(String key,Object value){ write.lock(); try { read.lock(); map.put(key, value); } finally { write.unlock(); } try { return map.get(key); } finally { read.unlock(); } }