bilibili-Java併發學習筆記15 ReentrantReadWriteLock 概覽
基於 java 1.8.0
package java.util.concurrent.locks;
/**
* ReadWriteLock 維護了一對相關的鎖,一個用於只讀操作,另一個用於寫入操作。
* 只要沒有 writer,讀取鎖可以由多個 reader 線程同時保持。寫入鎖是獨佔的。
*
* 所有 ReadWriteLock 實現都必須保證 writeLock 操作的內存同步效果
* 也要保持與相關 readLock 的聯繫。也就是說,成功獲取讀鎖的線程會看到寫入鎖之前版本所做的所有更新。
*
* 與互斥鎖相比,讀-寫鎖允許對共享數據進行更高級別的併發訪問。
* 雖然一次只有一個線程(writer 線程)可以修改共享數據,但在許多情況下,任何數量的線程可以同時讀取共享數據(reader 線程),讀-寫鎖利用了這一點。
* 從理論上講,與互斥鎖相比,使用讀-寫鎖所允許的併發性增強將帶來更大的性能提高。在實踐中,只有在多處理器上並且只在訪問模式適用於共享數據時,才能完全實現併發性增強。
*
* 與互斥鎖相比,使用讀-寫鎖能否提升性能則取決於讀寫操作期間讀取數據相對於修改數據的頻率,以及數據的爭用——即在同一時間試圖對該數據執行讀取或寫入操作的線程數。
* 例如,某個最初用數據填充並且之後不經常對其進行修改的 collection,因爲經常對其進行搜索(比如搜索某種目錄),所以這樣的 collection 是使用讀-寫鎖的理想候選者。
* 但是,如果數據更新變得頻繁,數據在大部分時間都被獨佔鎖,這時,就算存在併發性增強,也是微不足道的。
* 更進一步地說,如果讀取操作所用時間太短,則讀-寫鎖實現(它本身就比互斥鎖複雜)的開銷將成爲主要的執行成本,在許多讀-寫鎖實現仍然通過一小段代碼將所有線程序列化時更是如此。
* 最終,只有通過分析和測量,才能確定應用程序是否適合使用讀-寫鎖。
*
* 儘管讀-寫鎖的基本操作是直截了當的,但實現仍然必須作出許多決策,這些決策可能會影響給定應用程序中讀-寫鎖的效果。這些策略的例子包括:
* <li>在 writer 釋放寫入鎖時,reader 和 writer 都處於等待狀態,在這時要確定是授予讀取鎖還是授予寫入鎖。Writer 優先比較普遍,因爲預期寫入所需的時間較短並且不那麼頻繁。Reader 優先不太普遍,因爲如果 reader 正如預期的那樣頻繁和持久,那麼它將導致對於寫入操作來說較長的時延。公平或者“按次序”實現也是有可能的。
* <li>在 reader 處於活動狀態而 writer 處於等待狀態時,確定是否向請求讀取鎖的 reader 授予讀取鎖。Reader 優先會無限期地延遲 writer,而 writer 優先會減少可能的併發。
* <li>確定是否重新進入鎖:可以使用帶有寫入鎖的線程重新獲取它嗎?可以在保持寫入鎖的同時獲取讀取鎖嗎?可以重新進入寫入鎖本身嗎?
* <li>可以將寫入鎖在不允許其他 writer 干涉的情況下降級爲讀取鎖嗎?可以優先於其他等待的 reader 或 writer 將讀取鎖升級爲寫入鎖嗎?
*
* 當評估給定實現是否適合您的應用程序時,應該考慮所有這些情況。
*
* @see ReentrantReadWriteLock
* @see Lock
* @see ReentrantLock
*
* @since 1.5
* @author Doug Lea
*/
public interface ReadWriteLock {
/**
* 讀鎖(共享鎖)
*/
Lock readLock();
/**
* 寫鎖(互斥鎖)
*/
Lock writeLock();
}
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.Collection;
/**
* 支持與 ReentrantLock 類似語義的 ReadWriteLock 實現。
*
* 此類具有以下屬性:
* 獲取順序
*
* 此類不會將讀取者優先或寫入者優先強加給鎖訪問的排序。但是,它確實支持可選的公平 策略。
*
* 非公平模式(默認)
* 當非公平地(默認)構造時,未指定進入讀寫鎖的順序,受到 reentrancy 約束的限制。連續競爭的非公平鎖可能無限期地推遲一個或多個 reader 或 writer 線程,但吞吐量通常要高於公平鎖。
*
* 公平模式
* 當公平地構造線程時,線程利用一個近似到達順序的策略來爭奪進入。當釋放當前保持的鎖時,可以爲等待時間最長的單個 writer 線程分配寫入鎖,如果有一組等待時間大於所有正在等待的 writer 線程 的 reader 線程,將爲該組分配寫入鎖。
*
* 如果保持寫入鎖,或者有一個等待的 writer 線程,則試圖獲得公平讀取鎖(非重入地)的線程將會阻塞。直到當前最舊的等待 writer 線程已獲得並釋放了寫入鎖之後,該線程纔會獲得讀取鎖。當然,如果等待 writer 放棄其等待,而保留一個或更多 reader 線程爲隊列中帶有寫入鎖自由的時間最長的 waiter,則將爲那些 reader 分配讀取鎖。
*
* 試圖獲得公平寫入鎖的(非重入地)的線程將會阻塞,除非讀取鎖和寫入鎖都自由(這意味着沒有等待線程)。(注意,非阻塞 ReentrantReadWriteLock.ReadLock.tryLock() 和 ReentrantReadWriteLock.WriteLock.tryLock() 方法不會遵守此公平設置,並將獲得鎖(如果可能),不考慮等待線程)。
*
* 重入
*
* 此鎖允許 reader 和 writer 按照 ReentrantLock 的樣式重新獲取讀取鎖或寫入鎖。在寫入線程保持的所有寫入鎖都已經釋放後,才允許重入 reader 使用它們。
*
* 此外,writer 可以獲取讀取鎖,但反過來則不成立。在其他應用程序中,當在調用或回調那些在讀取鎖狀態下執行讀取操作的方法期間保持寫入鎖時,重入很有用。如果 reader 試圖獲取寫入鎖,那麼將永遠不會獲得成功。
*
* 鎖降級
* 重入還允許從寫入鎖降級爲讀取鎖,其實現方式是:先獲取寫入鎖,然後獲取讀取鎖,最後釋放寫入鎖。但是,從讀取鎖升級到寫入鎖是不可能的。
*
* 鎖獲取的中斷
* 讀取鎖和寫入鎖都支持鎖獲取期間的中斷。
*
* Condition 支持
* 寫入鎖提供了一個 Condition 實現,對於寫入鎖來說,該實現的行爲與 ReentrantLock.newCondition() 提供的 Condition 實現對 ReentrantLock 所做的行爲相同。當然,此 Condition 只能用於寫入鎖。
*
* 讀取鎖不支持 Condition,readLock().newCondition() 會拋出 UnsupportedOperationException。
*
* 監測
* 此類支持一些確定是保持鎖還是爭用鎖的方法。這些方法設計用於監視系統狀態,而不是同步控制
*
* 此類行爲的序列化方式與內置鎖的相同:反序列化的鎖處於解除鎖狀態,無論序列化該鎖時其狀態如何。
*
* 示例用法。下面的代碼展示瞭如何利用重入來執行升級緩存後的鎖降級(爲簡單起見,省略了異常處理):
*
* class CachedData {
* Object data;
* volatile boolean cacheValid;
* final 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();
* try {
* // 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();
* } finally {
* rwl.writeLock().unlock(); // Unlock write, still hold read
* }
* }
*
* try {
* use(data);
* } finally {
* rwl.readLock().unlock();
* }
* }
* }}
*
* 在使用某些種類的 Collection 時,可以使用 ReentrantReadWriteLock 來提高併發性。通常,在預期 collection 很大,讀取者線程訪問它的次數多於寫入者線程,並且 entail 操作的開銷高於同步開銷時,這很值得一試。例如,以下是一個使用 TreeMap 的類,預期它很大,並且能被同時訪問。
*
* 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(); }
* }
* }}
*
* 實現注意事項:
*
* 此鎖最多支持 65535 個遞歸寫入鎖和 65535 個讀取鎖。試圖超出這些限制將導致鎖方法拋出 Error。
*
* @since 1.5
* @author Doug Lea
*/
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
/**
* 使用默認(非公平)的排序屬性創建一個新的 ReentrantReadWriteLock。
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* 使用給定的公平策略創建一個新的 ReentrantReadWriteLock。
*
* @param fair 如果此鎖應該使用公平排序策略,則該參數的值爲 true
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
/**
* Synchronization implementation for ReentrantReadWriteLock.
* Subclassed into fair and nonfair versions.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
/**
* A counter for per-thread read hold counts.
* Maintained as a ThreadLocal; cached in cachedHoldCounter
*/
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
/**
* ThreadLocal subclass. Easiest to explicitly define for sake
* of deserialization mechanics.
*/
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
/**
* The number of reentrant read locks held by current thread.
* Initialized only in constructor and readObject.
* Removed whenever a thread's read hold count drops to 0.
*/
private transient ThreadLocalHoldCounter readHolds;
/**
* The hold count of the last thread to successfully acquire
* readLock. This saves ThreadLocal lookup in the common case
* where the next thread to release is the last one to
* acquire. This is non-volatile since it is just used
* as a heuristic, and would be great for threads to cache.
*
* <p>Can outlive the Thread for which it is caching the read
* hold count, but avoids garbage retention by not retaining a
* reference to the Thread.
*
* <p>Accessed via a benign data race; relies on the memory
* model's final field and out-of-thin-air guarantees.
*/
private transient HoldCounter cachedHoldCounter;
/**
* firstReader is the first thread to have acquired the read lock.
* firstReaderHoldCount is firstReader's hold count.
*
* <p>More precisely, firstReader is the unique thread that last
* changed the shared count from 0 to 1, and has not released the
* read lock since then; null if there is no such thread.
*
* <p>Cannot cause garbage retention unless the thread terminated
* without relinquishing its read locks, since tryReleaseShared
* sets it to null.
*
* <p>Accessed via a benign data race; relies on the memory
* model's out-of-thin-air guarantees for references.
*
* <p>This allows tracking of read holds for uncontended read
* locks to be very cheap.
*/
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
/*
* Acquires and releases use the same code for fair and
* nonfair locks, but differ in whether/how they allow barging
* when queues are non-empty.
*/
/**
* Returns true if the current thread, when trying to acquire
* the read lock, and otherwise eligible to do so, should block
* because of policy for overtaking other waiting threads.
*/
abstract boolean readerShouldBlock();
/**
* Returns true if the current thread, when trying to acquire
* the write lock, and otherwise eligible to do so, should block
* because of policy for overtaking other waiting threads.
*/
abstract boolean writerShouldBlock();
// ...
}
/**
* Nonfair version of Sync
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
/**
* Fair version of Sync
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
/**
* The lock returned by method {@link ReentrantReadWriteLock#readLock}.
*/
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
/**
* Constructor for use by subclasses
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// ...
}
/**
* The lock returned by method {@link ReentrantReadWriteLock#writeLock}.
*/
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
/**
* Constructor for use by subclasses
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// ...
}
// ...
}