ReentrantReadWriteLock可重入读写锁分析
ReentrantReadWriteLock 可重入的讀寫鎖
什么叫可重入:就是同一個線程可以重復加鎖,可以對同一個鎖加多次,每次釋放的時候回釋放一次,直到該線程加鎖次數為0,這個線程才釋放鎖。
什么叫讀寫鎖: 也就是讀鎖可以共享,多個線程可以同時擁有讀鎖,但是寫鎖卻只能只有一個線程擁有,而且獲取寫鎖的時候,其他線程都已經釋放了讀鎖,而且在該線程獲取寫鎖之后,其他線程不能再獲取讀鎖。
?
我們先看下下面兩個示例:?ReentrantReadWriteLock.java自帶的兩個示例
* 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();* }* }
如果要對cache的內容進行更新,則必須得加寫鎖,如果只是讀取,則加讀鎖就可以了。
* class RWDictionary {* private final Map<String, Data> m = new TreeMap<String, Data>();* private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();* private final Lock r = rwl.readLock();* private final Lock w = rwl.writeLock();** public Data get(String key) {* r.lock();* try { return m.get(key); }* finally { r.unlock(); }* }* public String[] allKeys() {* r.lock();* try { return m.keySet().toArray(); }* finally { r.unlock(); }* }* public Data put(String key, Data value) {* w.lock();* try { return m.put(key, value); }* finally { w.unlock(); }* }* public void clear() {* w.lock();* try { m.clear(); }* finally { w.unlock(); }* }* }}
這個示例,同樣,對map進行get,allKeys讀操作加讀鎖,put,clear操作加寫鎖。
?
我們先看下ReentrantReadWriteLock這個類的兩個構造函數:
public ReentrantReadWriteLock() {this(false);}/*** Creates a new {@code ReentrantReadWriteLock} with* the given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantReadWriteLock(boolean fair) {sync = (fair)? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);}
fair這個參數表示是否是創(chuàng)建一個公平的讀寫鎖,還是非公平的讀寫鎖。也就是搶占式還是非搶占式。
?
公平和非公平:公平表示獲取的鎖的順序是按照線程加鎖的順序來分配獲取到鎖的線程時最先加鎖的線程,是按照FIFO的順序來分配鎖的;非公平表示獲取鎖的順序是無需的,后來加鎖的線程可能先獲得鎖,這種情況就導致某些線程可能一直沒獲取到鎖。
?
公平鎖為啥會影響性能,從code上來看看公平鎖僅僅是多了一項檢查是否在隊首會影響性能,如不是,那么又是在什么地方影響的?假如是闖入的線程,會排在隊尾并睡覺(parking)等待前任節(jié)點喚醒,這樣勢必會比非公平鎖添加很多paking和unparking的操作
?
一般的應用場景是: 如果有多個讀線程,一個寫線程,而且寫線程在操作的時候需要阻塞讀線程,那么此時就需要使用公平鎖,要不然可能寫線程一直獲取不到鎖,導致線程餓死。
?
我這邊在一個項目就用到了讀寫鎖:
?
一個KV引擎的java客戶端:java客戶端需要緩存KV引擎服務器的集群配置信息(master server,data node service)多個map的數據結構;更新時由后臺一個寫線程定時更新;而讀取則同時可能幾十個線程同時讀,因為需要讀取配置定時定位到一個key對應的data nodeserver去獲取數據;為了更好的避免這些緩存的數據讀寫同步導致的問題,所以使用讀寫鎖來解決同步的問題。讀的時候加讀鎖,寫的時候加寫鎖,在寫的過程中需要阻塞讀,因為寫的過程非常快,所以可以阻塞讀。關鍵是寫的過程不能讓讀線程讀取到一部分數據是舊的,一部分是新的,導致獲取結果失敗甚至出錯。而這種case顯然是讀線程多,寫線程只有一個,在測試過程中就發(fā)現寫線程一直獲取不到鎖,因為用的是非公平鎖,所以后來通過查詢API,使用公平鎖就可以了。
?
?
下面我們來看下具體讀寫鎖的實現:
?
獲取讀鎖的過程:
?
protected final int tryAcquireShared(int unused) {/** Walkthrough:* 1. If write lock held by another thread, fail* 2. If count saturated, throw error* 3. Otherwise, this thread is eligible for* lock wrt state, so ask if it should block* because of queue policy. If not, try* to grant by CASing state and updating count.* Note that step does not check for reentrant* acquires, which is postponed to full version* to avoid having to check hold count in* the more typical non-reentrant case.* 4. If step 3 fails either because thread* apparently not eligible or CAS fails,* chain to version with full retry loop.*/Thread current = Thread.currentThread();int c = getState();if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return -1;if (sharedCount(c) == MAX_COUNT)throw new Error("Maximum lock count exceeded");if (!readerShouldBlock(current) &&compareAndSetState(c, c + SHARED_UNIT)) {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != current.getId())cachedHoldCounter = rh = readHolds.get();rh.count++;return 1;}return fullTryAcquireShared(current);} /*** Full version of acquire for reads, that handles CAS misses* and reentrant reads not dealt with in tryAcquireShared.*/final int fullTryAcquireShared(Thread current) {/** This code is in part redundant with that in* tryAcquireShared but is simpler overall by not* complicating tryAcquireShared with interactions between* retries and lazily reading hold counts.*/HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != current.getId())rh = readHolds.get();for (;;) {int c = getState();int w = exclusiveCount(c);if ((w != 0 && getExclusiveOwnerThread() != current) ||((rh.count | w) == 0 && readerShouldBlock(current)))return -1;if (sharedCount(c) == MAX_COUNT)throw new Error("Maximum lock count exceeded");if (compareAndSetState(c, c + SHARED_UNIT)) {cachedHoldCounter = rh; // cache for releaserh.count++;return 1;}}}如果寫鎖已經被獲取了,則獲取讀鎖失敗;如果當前線程重入加鎖次數達到MAX_COUNT,獲取讀鎖失敗;readerShouldBlock如果是公平鎖,則判斷當然線程是否是排在隊列前面,如果不是,則等待,是則獲取讀鎖;可重入性是通過class ThreadLocalHoldCounter?extends ThreadLocal<HoldCounter> threadlocal來保存重入獲取鎖的次數;然后就是調用fullTryAcquireShared方法對當前線程獲取鎖的次數進行操作。
?
?
獲取寫鎖的過程:
?
? 公平鎖獲取鎖的過程:
* Fair version of tryAcquire. Don't grant access unless* recursive call or no waiters or is first.*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (isFirst(current) &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}判斷當前線程是否是等待隊列的頭線程,如果是,則把當前的排斥鎖線程設置為當前線程,獲取寫鎖成功;否則判斷當前寫鎖的線程是不是當前線程,如果是則對寫鎖的重入數量進行加1操作;否則獲取寫鎖失敗。
?
非公平的寫鎖獲取過程:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
跟公平寫鎖獲取過程不同的是沒有判斷當前線程是否是等待隊列線程的第一個。
轉載于:https://www.cnblogs.com/secbook/archive/2012/07/05/2655158.html
總結
以上是生活随笔為你收集整理的ReentrantReadWriteLock可重入读写锁分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: exp4me 用java做的实用的csv
- 下一篇: Citrix XenApp下一些解决和未