一、前言
在分析了鎖框架的其他類之後,下面進入鎖框架中最後一個類ReentrantReadWriteLock的分析,它表示可重入讀寫鎖,ReentrantReadWriteLock中包含了兩種鎖,讀鎖ReadLock和寫鎖WriteLock,可以通過這兩種鎖實現執行緒間的同步,下面開始進行分析。
二、ReentrantReadWriteLock資料結構
分析原始碼可以知道,ReentrantReadWriteLock底層是基於ReentrantLock和AbstractQueuedSynchronizer來實現的,所以,ReentrantReadWriteLock的資料結構也依託於AQS的資料結構,在前面對AQS的分析中已經指出了其資料結構,在這裡不再累贅。
三、ReentrantReadWriteLock原始碼分析
3.1. 類的繼承關係
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {}
說明:可以看到,ReentrantReadWriteLock實現了ReadWriteLock介面,ReadWriteLock介面定義了獲取讀鎖和寫鎖的規範,具體需要實現類去實現;同時其還實現了Serializable介面,表示可以進行序列化,在原始碼中可以看到ReentrantReadWriteLock實現了自己的序列化邏輯。
3.2. 類的內部類
ReentrantReadWriteLock有五個內部類,五個內部類之間也是相互關聯的。內部類的關係如下圖所示。
說明:如上圖所示,Sync繼承自AQS、NonfairSync繼承自Sync類、FairSync繼承自Sync類;ReadLock實現了Lock介面、WriteLock也實現了Lock介面。
① Sync類
1. 類的繼承關係
abstract static class Sync extends AbstractQueuedSynchronizer {}
說明:Sync抽象類繼承自AQS抽象類,Sync類提供了對ReentrantReadWriteLock的支援。
2. 類的內部類
Sync類內部存在兩個內部類,分別為HoldCounter和ThreadLocalHoldCounter,其中HoldCounter主要與讀鎖配套使用,其中,HoldCounter原始碼如下。
// 計數器 static final class HoldCounter { // 計數 int count = 0; // Use id, not reference, to avoid garbage retention // 獲取當前執行緒的TID屬性的值 final long tid = getThreadId(Thread.currentThread()); }
說明:HoldCounter主要有兩個屬性,count和tid,其中count表示某個讀執行緒重入的次數,tid表示該執行緒的tid欄位的值,該欄位可以用來唯一標識一個執行緒。ThreadLocalHoldCounter的原始碼如下
// 本地執行緒計數器 static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { // 重寫初始化方法,在沒有進行set的情況下,獲取的都是該HoldCounter值 public HoldCounter initialValue() { return new HoldCounter(); } }
說明:ThreadLocalHoldCounter重寫了ThreadLocal的initialValue方法,ThreadLocal類可以將執行緒與物件相關聯。在沒有進行set的情況下,get到的均是initialValue方法裡面生成的那個HolderCounter物件。
3. 類的屬性
abstract static class Sync extends AbstractQueuedSynchronizer { // 版本序列號 private static final long serialVersionUID = 6317671515068378041L; // 高16位為讀鎖,低16位為寫鎖 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; // 本地執行緒計數器 private transient ThreadLocalHoldCounter readHolds; // 快取的計數器 private transient HoldCounter cachedHoldCounter; // 第一個讀執行緒 private transient Thread firstReader = null; // 第一個讀執行緒的計數 private transient int firstReaderHoldCount; }
說明:該屬性中包括了讀鎖、寫鎖執行緒的最大量。本地執行緒計數器等。
4. 類的建構函式
// 建構函式 Sync() { // 本地執行緒計數器 readHolds = new ThreadLocalHoldCounter(); // 設定AQS的狀態 setState(getState()); // ensures visibility of readHolds }
說明:在Sync的建構函式中設定了本地執行緒計數器和AQS的狀態state。
5. 核心函式分析
對ReentrantReadWriteLock物件的操作絕大多數都轉發至Sync物件進行處理。下面對Sync類中的重點函式進行分析
I. sharedCount函式
表示佔有讀鎖的執行緒數量,原始碼如下
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
說明:直接將state右移16位,就可以得到讀鎖的執行緒數量,因為state的高16位表示讀鎖,對應的第十六位表示寫鎖數量。
II. exclusiveCount函式
表示佔有寫鎖的執行緒數量,原始碼如下
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
說明:直接將狀態state和(2^16 - 1)做與運算,其等效於將state模上2^16。寫鎖數量由state的低十六位表示。
III. tryRelease函式
protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ // 獲取當前執行緒 Thread current = Thread.currentThread(); // 獲取狀態 int c = getState(); // 寫執行緒數量 int w = exclusiveCount(c); if (c != 0) { // 狀態不為0 // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) // 寫執行緒數量為0或者當前執行緒沒有佔有獨佔資源 return false; if (w + exclusiveCount(acquires) > MAX_COUNT) // 判斷是否超過最高寫執行緒數量 throw new Error("Maximum lock count exceeded"); // Reentrant acquire // 設定AQS狀態 setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 寫執行緒是否應該被阻塞 return false; // 設定獨佔執行緒 setExclusiveOwnerThread(current); return true; }
說明:此函式用於釋放寫鎖資源,首先會判斷該執行緒是否為獨佔執行緒,若不為獨佔執行緒,則丟擲異常,否則,計算釋放資源後的寫鎖的數量,若為0,表示成功釋放,資源不將被佔用,否則,表示資源還被佔用。其函式流程圖如下。
IV. tryAcquire函式
protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ // 獲取當前執行緒 Thread current = Thread.currentThread(); // 獲取狀態 int c = getState(); // 寫執行緒數量 int w = exclusiveCount(c); if (c != 0) { // 狀態不為0 // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) // 寫執行緒數量為0或者當前執行緒沒有佔有獨佔資源 return false; if (w + exclusiveCount(acquires) > MAX_COUNT) // 判斷是否超過最高寫執行緒數量 throw new Error("Maximum lock count exceeded"); // Reentrant acquire // 設定AQS狀態 setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 寫執行緒是否應該被阻塞 return false; // 設定獨佔執行緒 setExclusiveOwnerThread(current); return true; }
說明:此函式用於獲取寫鎖,首先會獲取state,判斷是否為0,若為0,表示此時沒有讀鎖執行緒,再判斷寫執行緒是否應該被阻塞,而在非公平策略下總是不會被阻塞,在公平策略下會進行判斷(判斷同步佇列中是否有等待時間更長的執行緒,若存在,則需要被阻塞,否則,無需阻塞),之後在設定狀態state,然後返回true。若state不為0,則表示此時存在讀鎖或寫鎖執行緒,若寫鎖執行緒數量為0或者當前執行緒為獨佔鎖執行緒,則返回false,表示不成功,否則,判斷寫鎖執行緒的重入次數是否大於了最大值,若是,則丟擲異常,否則,設定狀態state,返回true,表示成功。其函式流程圖如下
V. tryReleaseShared函式
protected final boolean tryReleaseShared(int unused) { // 獲取當前執行緒 Thread current = Thread.currentThread(); if (firstReader == current) { // 當前執行緒為第一個讀執行緒 // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) // 讀執行緒佔用的資源數為1 firstReader = null; else // 減少佔用的資源 firstReaderHoldCount--; } else { // 當前執行緒不為第一個讀執行緒 // 獲取快取的計數器 HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) // 計數器為空或者計數器的tid不為當前正在執行的執行緒的tid // 獲取當前執行緒對應的計數器 rh = readHolds.get(); // 獲取計數 int count = rh.count; if (count <= 1) { // 計數小於等於1 // 移除 readHolds.remove(); if (count <= 0) // 計數小於等於0,丟擲異常 throw unmatchedUnlockException(); } // 減少計數 --rh.count; } for (;;) { // 無限迴圈 // 獲取狀態 int c = getState(); // 獲取狀態 int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // 比較並進行設定 // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; } }
說明:此函式表示讀鎖執行緒釋放鎖。首先判斷當前執行緒是否為第一個讀執行緒firstReader,若是,則判斷第一個讀執行緒佔有的資源數firstReaderHoldCount是否為1,若是,則設定第一個讀執行緒firstReader為空,否則,將第一個讀執行緒佔有的資源數firstReaderHoldCount減1;若當前執行緒不是第一個讀執行緒,那麼首先會獲取快取計數器(上一個讀鎖執行緒對應的計數器 ),若計數器為空或者tid不等於當前執行緒的tid值,則獲取當前執行緒的計數器,如果計數器的計數count小於等於1,則移除當前執行緒對應的計數器,如果計數器的計數count小於等於0,則丟擲異常,之後再減少計數即可。無論何種情況,都會進入無限迴圈,該迴圈可以確保成功設定狀態state。其流程圖如下
VI. tryAcquireShared函式
private IllegalMonitorStateException unmatchedUnlockException() { return new IllegalMonitorStateException( "attempt to unlock read lock, not locked by current thread"); } // 共享模式下獲取資源 protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. 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. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ // 獲取當前執行緒 Thread current = Thread.currentThread(); // 獲取狀態 int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) // 寫執行緒數不為0並且佔有資源的不是當前執行緒 return -1; // 讀鎖數量 int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 讀執行緒是否應該被阻塞、並且小於最大值、並且比較設定成功 if (r == 0) { // 讀鎖數量為0 // 設定第一個讀執行緒 firstReader = current; // 讀執行緒佔用的資源數為1 firstReaderHoldCount = 1; } else if (firstReader == current) { // 當前執行緒為第一個讀執行緒 // 佔用資源數加1 firstReaderHoldCount++; } else { // 讀鎖數量不為0並且不為當前執行緒 // 獲取計數器 HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) // 計數器為空或者計數器的tid不為當前正在執行的執行緒的tid // 獲取當前執行緒對應的計數器 cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) // 計數為0 // 設定 readHolds.set(rh); rh.count++; } return 1; } return fullTryAcquireShared(current); }
說明:此函式表示讀鎖執行緒獲取讀鎖。首先判斷寫鎖是否為0並且當前執行緒不佔有獨佔鎖,直接返回;否則,判斷讀執行緒是否需要被阻塞並且讀鎖數量是否小於最大值並且比較設定狀態成功,若當前沒有讀鎖,則設定第一個讀執行緒firstReader和firstReaderHoldCount;若當前執行緒執行緒為第一個讀執行緒,則增加firstReaderHoldCount;否則,將設定當前執行緒對應的HoldCounter物件的值。流程圖如下。
VII. fullTryAcquireShared函式
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 = null; for (;;) { // 無限迴圈 // 獲取狀態 int c = getState(); if (exclusiveCount(c) != 0) { // 寫執行緒數量不為0 if (getExclusiveOwnerThread() != current) // 不為當前執行緒 return -1; // else we hold the exclusive lock; blocking here // would cause deadlock. } else if (readerShouldBlock()) { // 寫執行緒數量為0並且讀執行緒被阻塞 // Make sure we're not acquiring read lock reentrantly if (firstReader == current) { // 當前執行緒為第一個讀執行緒 // assert firstReaderHoldCount > 0; } else { // 當前執行緒不為第一個讀執行緒 if (rh == null) { // 計數器不為空 // rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { // 計數器為空或者計數器的tid不為當前正在執行的執行緒的tid rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } if (sharedCount(c) == MAX_COUNT) // 讀鎖數量為最大值,丟擲異常 throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { // 比較並且設定成功 if (sharedCount(c) == 0) { // 讀執行緒數量為0 // 設定第一個讀執行緒 firstReader = current; // firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } }
說明:在tryAcquireShared函式中,如果下列三個條件不滿足(讀執行緒是否應該被阻塞、小於最大值、比較設定成功)則會進行fullTryAcquireShared函式中,它用來保證相關操作可以成功。其邏輯與tryAcquireShared邏輯類似,不再累贅。
而其他內部類的操作基本上都是轉化到了對Sync物件的操作,在此不再累贅。
3.3. 類的屬性
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { // 版本序列號 private static final long serialVersionUID = -6992448646407690164L; // 讀鎖 private final ReentrantReadWriteLock.ReadLock readerLock; // 寫鎖 private final ReentrantReadWriteLock.WriteLock writerLock; // 同步佇列 final Sync sync; private static final sun.misc.Unsafe UNSAFE; // 執行緒ID的偏移地址 private static final long TID_OFFSET; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> tk = Thread.class; // 獲取執行緒的tid欄位的記憶體地址 TID_OFFSET = UNSAFE.objectFieldOffset (tk.getDeclaredField("tid")); } catch (Exception e) { throw new Error(e); } } }
說明:可以看到ReentrantReadWriteLock屬性包括了一個ReentrantReadWriteLock.ReadLock物件,表示讀鎖;一個ReentrantReadWriteLock.WriteLock物件,表示寫鎖;一個Sync物件,表示同步佇列。
3.4. 類的建構函式
1. ReentrantReadWriteLock()型建構函式
public ReentrantReadWriteLock() { this(false); }
說明:此建構函式會呼叫另外一個有參建構函式。
2. ReentrantReadWriteLock(boolean)型建構函式
public ReentrantReadWriteLock(boolean fair) { // 公平策略或者是非公平策略 sync = fair ? new FairSync() : new NonfairSync(); // 讀鎖 readerLock = new ReadLock(this); // 寫鎖 writerLock = new WriteLock(this); }
說明:可以指定設定公平策略或者非公平策略,並且該建構函式中生成了讀鎖與寫鎖兩個物件。
3.5 核心函式分析
對ReentrantReadWriteLock的操作基本上都轉化為了對Sync物件的操作,而Sync的函式已經分析過,不再累贅。
四、示例
下面給出了一個使用ReentrantReadWriteLock的示例,原始碼如下。
package com.hust.grid.leesf.reentrantreadwritelock; import java.util.concurrent.locks.ReentrantReadWriteLock; class ReadThread extends Thread { private ReentrantReadWriteLock rrwLock; public ReadThread(String name, ReentrantReadWriteLock rrwLock) { super(name); this.rrwLock = rrwLock; } public void run() { System.out.println(Thread.currentThread().getName() + " trying to lock"); try { rrwLock.readLock().lock(); System.out.println(Thread.currentThread().getName() + " lock successfully"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { rrwLock.readLock().unlock(); System.out.println(Thread.currentThread().getName() + " unlock successfully"); } } } class WriteThread extends Thread { private ReentrantReadWriteLock rrwLock; public WriteThread(String name, ReentrantReadWriteLock rrwLock) { super(name); this.rrwLock = rrwLock; } public void run() { System.out.println(Thread.currentThread().getName() + " trying to lock"); try { rrwLock.writeLock().lock(); System.out.println(Thread.currentThread().getName() + " lock successfully"); } finally { rrwLock.writeLock().unlock(); System.out.println(Thread.currentThread().getName() + " unlock successfully"); } } } public class ReentrantReadWriteLockDemo { public static void main(String[] args) { ReentrantReadWriteLock rrwLock = new ReentrantReadWriteLock(); ReadThread rt1 = new ReadThread("rt1", rrwLock); ReadThread rt2 = new ReadThread("rt2", rrwLock); WriteThread wt1 = new WriteThread("wt1", rrwLock); rt1.start(); rt2.start(); wt1.start(); } }
執行結果(某一次):
rt1 trying to lock
rt2 trying to lock
wt1 trying to lock
rt1 lock successfully
rt2 lock successfully
rt1 unlock successfully
rt2 unlock successfully
wt1 lock successfully
wt1 unlock successfully
說明:程式中生成了一個ReentrantReadWriteLock物件,並且設定了兩個讀執行緒,一個寫執行緒。根據結果,可能存在如下的時序圖。
① rt1執行緒執行rrwLock.readLock().lock操作,主要的函式呼叫如下。
說明:此時,AQS的狀態state為2^16 次方,即表示此時讀執行緒數量為1。
② rt2執行緒執行rrwLock.readLock().lock操作,主要的函式呼叫如下。
說明:此時,AQS的狀態state為2 * 2^16次方,即表示此時讀執行緒數量為2。
③ wt1執行緒執行rrwLock.writeLock().lock操作,主要的函式呼叫如下。
說明:此時,在同步佇列Sync queue中存在兩個結點,並且wt1執行緒會被禁止執行。
④ rt1執行緒執行rrwLock.readLock().unlock操作,主要的函式呼叫如下。
說明:此時,AQS的state為2^16次方,表示還有一個讀執行緒。
⑤ rt2執行緒執行rrwLock.readLock().unlock操作,主要的函式呼叫如下。
說明:當rt2執行緒執行unlock操作後,AQS的state為0,並且wt1執行緒將會被unpark,其獲得CPU資源就可以執行。
⑥ wt1執行緒獲得CPU資源,繼續執行,需要恢復。由於之前acquireQueued函式中的parkAndCheckInterrupt函式中被禁止的,所以,恢復到parkAndCheckInterrupt函式中,主要的函式呼叫如下
說明:最後,sync queue佇列中只有一個結點,並且頭結點尾節點均指向它,AQS的state值為1,表示此時有一個寫執行緒。
⑦ wt1執行rrwLock.writeLock().unlock操作,主要的函式呼叫如下。
說明:此時,AQS的state為0,表示沒有任何讀執行緒或者寫執行緒了。並且Sync queue結構與上一個狀態的結構相同,沒有變化。
五、總結
經過分析ReentrantReadWriteLock的原始碼,可知其可以實現多個執行緒同時讀,此時,寫執行緒會被阻塞。並且,寫執行緒獲取寫入鎖後可以獲取讀取鎖,然後釋放寫入鎖,這樣寫入鎖變成了讀取鎖。至此,併發框架中的鎖框架就已經全部介紹完成了,通過分析原始碼,有了不少收穫,謝謝各位園友的觀看~