1. 用法
1.1 定義一個安全的list集合
public class LockDemo {
ArrayList<Integer> arrayList = new ArrayList<>();//定義一個集合
// 定義讀鎖
ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock(true).readLock();
// 定義寫鎖
ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock(true).writeLock();
public void addEle(Integer ele) {
writeLock.lock(); // 獲取寫鎖
arrayList.add(ele);
writeLock.unlock(); // 釋放寫鎖
}
public Integer getEle(Integer index) {
try{
readLock.lock(); // 獲取讀鎖
Integer res = arrayList.get(index);
return res;
} finally{
readLock.unlock();// 釋放讀鎖
}
}
}
1.2 Sync 原始碼中的屬性與方法在上一篇文章中已經講過了
2. 獲取寫鎖原始碼分析
ReentrantReadWriteLock中的lock方法
public void lock() {
sync.acquire(1);
}
AbstractQueuedSynchronizer中的acquire方法
public final void acquire(int arg) {
// 獲取鎖失敗則進入阻塞佇列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))****,中的acquireQueued方法和addWaiter方法在前面的文章中都已經進行了詳細的解釋說明。
ReentrantReadWriteLock中的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
// 獲取當前執行緒
Thread current = Thread.currentThread();
// 獲取狀態
int c = getState();
// 計算寫執行緒數量就是獨佔鎖的可從入數量
int w = exclusiveCount(c);
// 當前同步狀態state != 0,說明已經有其他執行緒獲取了讀鎖或寫鎖
if (c != 0) {
// 當前state不為0,此時:如果寫鎖狀態為0說明讀鎖此時被佔用返回false;
// 如果寫鎖狀態不為0且寫鎖沒有被當前執行緒持有返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 判斷同一執行緒獲取寫鎖是否超過最大次數(65535),支援可重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//更新狀態
//此時當前執行緒已持有寫鎖,現在是重入,所以只需要修改鎖的數量即可
setState(c + acquires);
return true;
}
//到這裡說明此時c=0,讀鎖和寫鎖都沒有被獲取
//writerShouldBlock表示是否阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 設定鎖為當前執行緒所有
setExclusiveOwnerThread(current);
return true;
}
static final class FairSync extends Sync {
// 寫鎖是否應該被阻塞
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
}
3. 獲取寫鎖流程圖
3.1 流程圖獲取寫鎖過程
3.2 流程圖獲取寫鎖過程解析
寫鎖的獲取過程如下:
- 首先獲取c、w。c表示當前鎖狀態;w表示寫執行緒數量。然後判斷同步狀態state是否為0。如果state!=0,說明已經有其他執行緒獲取了讀鎖或寫鎖。
- 如果鎖狀態不為零(c != 0),而寫鎖的狀態為0(w = 0),說明讀鎖此時被其他執行緒佔用,所以當前執行緒不能獲取寫鎖,自然返回false。或者鎖狀態不為零,而寫鎖的狀態也不為0,但是獲取寫鎖的執行緒不是當前執行緒,則當前執行緒也不能獲取寫鎖。
- 判斷當前執行緒獲取寫鎖是否超過最大次數,若超過,拋異常,反之更新同步狀態(此時當前執行緒已獲取寫鎖,更新是執行緒安全的),返回true。
- 如果state為0,此時讀鎖或寫鎖都沒有被獲取,判斷是否需要阻塞(公平和非公平方式實現不同),在非公平策略下總是不會被阻塞,在公平策略下會進行判斷(判斷同步佇列中是否有等待時間更長的執行緒,若存在,則需要被阻塞,否則,無需阻塞),如果不需要阻塞,則CAS更新同步狀態,若CAS成功則返回true,失敗則說明鎖被別的執行緒搶去了,返回false。如果需要阻塞則也返回false。
- 成功獲取寫鎖後,將當前執行緒設定為佔有寫鎖的執行緒,返回true。
- 獲取鎖失敗的話,將當前執行緒進行放入阻塞佇列中。
4. 釋放寫鎖原始碼分析
ReentrantReadWriteLock中的unlock方法
public void unlock() {
sync.release(1);
}
AbstractQueuedSynchronizer中的release方法
public final boolean release(int arg) {
// 如果返回true 那麼釋放成功了
if (tryRelease(arg)) {
Node h = head;
// 如果頭部不為空,並且頭節點的waitStatus是喚醒狀態那麼喚醒後繼執行緒
if (h != null && h.waitStatus != 0)
// 喚醒後繼執行緒
unparkSuccessor(h);
return true;
}
return false;
}
ReentrantReadWriteLock中tryRelease方法
protected final boolean tryRelease(int releases) {
// 若鎖的持有者不是當前執行緒,丟擲異常
if (!isHeldExclusively())
// 非法的監控器異常
throw new IllegalMonitorStateException();
// 計算寫鎖的新執行緒數
int nextc = getState() - releases;
// 如果獨佔模式重入數為0了,說明獨佔模式被釋放
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 設定獨佔執行緒為空
setExclusiveOwnerThread(null);
// 設定寫鎖的新執行緒數
// 不管獨佔模式是否被釋放,更新獨佔重入數
setState(nextc);
return free;
}
protected final boolean isHeldExclusively() {
// 若當前執行緒是當前鎖的持有執行緒那麼返回true
return getExclusiveOwnerThread() == Thread.currentThread();
}
5. 釋放寫鎖流程圖
5.1 流程圖釋放過程
5.2 流程圖釋放過程解析
寫鎖的釋放過程:
- 首先檢視當前執行緒是否為寫鎖的持有者,如果不是丟擲異常。然後檢查釋放後寫鎖的執行緒數是否為0,如果為0則表示寫鎖空閒了,釋放鎖資源將鎖的持有執行緒設定為null,否則釋放僅僅只是一次重入鎖而已,並不能將寫鎖的執行緒清空。
- 說明:此方法用於釋放寫鎖資源,首先會判斷該執行緒是否為獨佔執行緒,若不為獨佔執行緒,則丟擲異常,否則,計算釋放資源後的寫鎖的數量,若為0,表示成功釋放,資源不將被佔用,否則,表示資源還被佔用。
6. 總結
6.1 state 解析
private volatile int state;
int 型別佔有 4個位元組一個位元組8位,所以 state 一個 32 位,高 16 位 代表讀鎖 低 16 位代表 寫鎖。
// 0x0000FFFF 16 進位制
// 1111111111111111 2 進位制
// 65535 10 進位制
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 65536
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //65535
// 1111111111111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 65535
// 1111111111111111
如果此時同步狀態位 c 那麼獲取寫狀態 c & EXCLUSIVE_MASK
如果此時同步狀態位 c 那麼獲取讀狀態 c >>>16 無符號補0,右移16位
6.2 注意
以上便是ReentrantReadWriteLock中寫鎖的分析,下一篇文章將是Condition的分析,如有錯誤之處,幫忙指出及時更正,謝謝,如果喜歡謝謝點贊加收藏加轉發(轉發註明出處謝謝!!!)