1. 程式人生 > >ReentrantReadWriteLock原始碼分析及理解

ReentrantReadWriteLock原始碼分析及理解

##本文結構 - 讀寫鎖簡介:介紹讀寫鎖、讀寫鎖的特性以及類定義資訊 - 公平策略及Sync同步器:介紹讀寫鎖提供的公平策略以及同步器原始碼分析 - 讀鎖:介紹讀鎖的一些常用操作和讀鎖的加鎖、解鎖的原始碼分析 - 寫鎖:介紹寫鎖的一些常用操作和寫鎖的加鎖、解鎖的原始碼分析 - 總結:總結全文,附讀寫鎖全部原始碼理解 ##讀寫鎖簡介 在之前的文章提到了可重入鎖,這是一種排他鎖,核心原理是同一時間只允許一個執行緒訪問。除了排他鎖還有一種共享鎖,這種鎖在同一時間支援多執行緒同時訪問, 將排他鎖和共享鎖進行組合便有了讀寫鎖。讀寫鎖維護了一組鎖——讀鎖和寫鎖。讀鎖在同一時間可以有多個執行緒共同訪問,是一個共享鎖;而寫鎖在同一時間僅支援 一個執行緒訪問,是一個排他鎖。通過讀鎖的允許多個執行緒同時訪問,使得併發性相比單純的排他鎖效率有很大的提升。 在讀寫鎖中,需要保證寫鎖對於讀鎖的可見性,也就是說當寫鎖更改資料之後讀鎖需要能夠立刻獲知。假設有一組執行緒訪問同一個快取區,其中只有一個執行緒向其中寫資料, 其他的執行緒都是讀資料,這塊區域大部分的時間都是使用者的讀操作,只有很少的時間是寫操作,多個執行緒的讀並不會相互影響,那麼就可以使用讀寫鎖,只需要保證寫操作 之後,資料立刻對於其他的讀操作可見即可。 $\color{#FF3030}{讀寫鎖是一個鎖,只是可以進行共享或者排他的兩種操作模式。}$讀寫鎖是一個鎖,只是可以進行共享或者排他的兩種操作模式。 ####讀寫鎖的特性 一般情況下,讀寫鎖的效能會優於排他鎖,因為程式中大多數場景都是讀取資料,很少一部分是寫資料。在讀取併發多的情況下,可以提供比排他鎖更好的吞吐量。 |特性|說明| |:---:|:---:| |公平性|讀寫鎖可以選擇公平和非公平性兩種特性,預設為非公平模式,並且吞吐量非公平由於公平模式| |可重入性|讀(寫)鎖支援執行緒的重入。當一個執行緒獲取讀(寫)鎖後,這個執行緒可以再次獲取這個讀(寫)鎖| |鎖降級|當一個執行緒獲取寫鎖之後,可以獲取讀鎖,在釋放寫鎖完成鎖降級過程| ####讀寫鎖的定義 `ReentrantReadWriteLock`簡單分析,主要介紹類由哪些部分組成及每部分的作用,具體的實現後面按照內部類及提供主要操作細解 ``` 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; /** * Creates a new {@code ReentrantReadWriteLock} with * default (nonfair) ordering properties. */ //預設的讀寫鎖建構函式,預設使用非公平模式 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 */ //帶公平策略選擇的構造器,其中引數為true代表公平模式,false代表非公平模式 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; } //同步器類,實現核心的加鎖解鎖同步佇列的操作,委派子類實現公平非公平策略 abstract static class Sync extends AbstractQueuedSynchronizer {......} //非公平模式同步器,繼承Sync並實現非公平策略 static final class NonfairSync extends Sync{....} //公平模式同步器,繼承Sync並實現公平策略 static final class FairSync extends Sync{....} //寫鎖類,使用與外部類一致的公平策略 public static class WriteLock implements Lock, java.io.Serializable{......} //讀鎖類,使用與外部類一致的公平策略 public static class ReadLock implements Lock, java.io.Serializable{......} ``` ##公平策略及Sync同步器 讀寫鎖提供了公平與非公平策略,並由`Sync`的子類實現。`NonfairSync`與`FairSync`主要用來判斷獲取讀鎖和寫鎖的時候是否需要阻塞, 其獲取鎖的過程全部交由`Sync`實現。也可以說使`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(); } } //這個方法在AQS中實現,目的就是執行的操作就是判斷佇列的頭節點的後繼節點是不是獲取寫鎖 final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; return (h = head) != null && (s = h.next) != null && !s.isShared() && s.thread != null; } ``` ####公平策略 公平版本的同步器,詳細解釋見原始碼註釋 ``` //相比非公平版本就會簡單很多,只需要判斷佇列中是否有現成在等待就可以 static final class FairSync extends Sync { private static final long serialVersionUID = -2274990926593161451L; final boolean writerShouldBlock() { return hasQueuedPredecessors(); } final boolean readerShouldBlock() { return hasQueuedPredecessors(); } } ``` ####Sync同步器 `Sync`實現了主要的加鎖解鎖的操作。讀寫鎖同樣依賴`AQS`來實現同步功能,在AQS中由`State`整型欄位代表所數量而這裡是兩種鎖,因此使用位操作來代表不同的鎖, 使用高16位代表共享鎖(讀鎖),低16位代表排他鎖(寫鎖)如下圖,並且所有與為操有關的事情都在這裡完成,`AQS`中僅提供一些判斷介面及佇列操作。 ![](https://img2020.cnblogs.com/blog/1669869/202006/1669869-20200604172946943-963535236.png) 具體程式碼解釋見註釋 ``` abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 6317671515068378041L; /* * 讀鎖和寫鎖的一些常數和函式 * 將鎖狀態在邏輯上分為兩個無符號的短整型 * 低位代表排他鎖(寫鎖)的計數器 * 高位代表共享鎖(讀鎖)的計數器 */ //共享鎖的偏移量——16位 static final int SHARED_SHIFT = 16; //共享鎖計數器加一或者減一的基數 static final int SHARED_UNIT = (1 << SHARED_SHIFT); //鎖計數器的最大值,做最大可以被同時或者重入獲取的次數 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //寫鎖的掩碼 和鎖狀態state按位與操作可以得到寫鎖的計數器 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //計算共享鎖的持有量(讀鎖) static int sharedCount(int c) { return c >>> SHARED_SHIFT; } //計算排他鎖的持有量(寫鎖) static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } /** * 每個執行緒持有的讀鎖的計數器. * 作為ThreadLocal維護; 最後一成功加鎖的執行緒資訊快取在cachedHoldCounter */ static final class HoldCounter { //執行緒持有的讀鎖的數量 int count = 0; // Use id, not reference, to avoid garbage retention //使用id而不使用引用避免垃圾保留 final long tid = getThreadId(Thread.currentThread()); } /** * ThreadLocal 子類. 重寫了initialValue方法, * 在第一次get時(之前也沒進行過set操作)返回count值為0而不是null */ static final class ThreadLocalHoldCounter extends Thr