掃描下方二維碼或者微信搜索公衆號
菜鳥飛呀飛
,便可關注微信公衆號,閱讀更多Spring源碼分析
和Java併發編程
文章。java
在閱讀本文以前能夠先思考一下幾個問題編程
排他鎖
,所謂排他鎖就是同一時刻只容許一個線程訪問共享資源,可是在平時場景中,咱們一般會碰到對於共享資源讀多寫少
的場景。對於讀場景,每次只容許一個線程訪問共享資源,顯然這種狀況使用排他鎖效率就比較低下,那麼該如何優化呢?讀寫鎖
就應運而生了,讀寫鎖是一種通用技術,並非Java特有的。從名字來看,讀寫鎖擁有兩把鎖,讀鎖
和寫鎖
。讀寫鎖的特色是:同一時刻容許多個線程對共享資源進行讀操做;同一時刻只容許一個線程對共享資源進行寫操做;當進行寫操做時,同一時刻其餘線程的讀操做會被阻塞;當進行讀操做時,同一時刻全部線程的寫操做會被阻塞。對於讀鎖而言,因爲同一時刻能夠容許多個線程訪問共享資源,進行讀操做,所以稱它爲共享鎖;而對於寫鎖而言,同一時刻只容許一個線程訪問共享資源,進行寫操做,所以稱它爲排他鎖。ReadWriteLock
來實現讀寫鎖。ReadWriteLock是一個接口,ReentrantReadWriteLock
是ReadWriteLock接口的具體實現類。在ReentrantReadWriteLock中定義了兩個內部類ReadLock
、WriteLock
,分別來實現讀鎖和寫鎖。ReentrantReadWriteLock底層是經過AQS來實現鎖的獲取與釋放的,所以ReentrantReadWriteLock內部還定義了一個繼承了AQS類的同步組件Sync
,同時ReentrantReadWriteLock還支持公平與非公平性
,所以它內部還定義了兩個內部類FairSync、NonfairSync
,它們繼承了Sync。方法名 | 功能 |
---|---|
int getReadLockCount() | 獲取讀鎖的數量,此時讀鎖的數量不必定等於獲取鎖的數量,由於鎖能夠重入,可能有線程重入了讀鎖 |
int getReadHoldCount() | 獲取當前線程重入讀鎖的次數 |
int getWriteHoldCount() | 獲取當前線程重入寫鎖的次數 |
int isWriteLocked() | 判斷鎖的狀態是不是寫鎖,返回true,表示鎖的狀態是寫鎖 |
int類型
的全局變量state來表示同步狀態,即用state來表示鎖。ReentrantReadWriteLock也是經過AQS來實現鎖的,可是ReentrantReadWriteLock有兩把鎖:讀鎖和寫鎖,它們保護的都是同一個資源,那麼如何用一個共享變量來區分鎖是寫鎖仍是讀鎖呢?答案就是按位拆分
。佔用4個字節,也就是32位
。將其拆分爲兩部分:高16位和低16位,其中高16位用來表示讀鎖狀態,低16位用來表示寫鎖狀態
。當設置讀鎖成功時,就將高16位加1,釋放讀鎖時,將高16位減1;當設置寫鎖成功時,就將低16位加1,釋放寫鎖時,將第16位減1。以下圖所示。0x0000FFFF
進行與運算
,即S&0x0000FFFF,運算時會將高16位全置爲0,將運算結果記爲c,那麼c表示的就是寫鎖的數量。若是c等於0就表示尚未線程獲取鎖;若是c不等於0,就表示有線程獲取到了鎖,c等於幾就表明寫鎖重入了幾回。無符號右移16位
(S>>>16),獲得的結果就是讀鎖的數量
。當S>>>16獲得的結果不等於0,且c也不等於0時,就表示當前線程既持有了寫鎖,也持有了讀鎖。理解了如何經過state來表示鎖的狀態,接下來將經過源碼來分析讀寫鎖的源碼實現。設計模式
默認建立的是非公平的讀寫鎖
。在讀寫鎖中,仍然是非公平的讀寫鎖性能要因爲公平的讀寫鎖
。ReadWriteLock lock = new ReentrantReadWriteLock();
// 建立讀鎖
Lock readLock = lock.readLock();
// 建立寫鎖
Lock writeLock = lock.writeLock();
複製代碼
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
// exclusiveCount()方法的做用是將同步變量與0xFFFF作&運算,計算結果就是寫鎖的數量。
// 所以w的值的含義就是寫鎖的數量
int w = exclusiveCount(c);
// 若是c不爲0就表示鎖被佔用了,可是佔用的是寫鎖仍是讀書呢?這個時候就須要根據w的值來判斷了。
// 若是c等於0就表示此時鎖尚未被任何線程佔用,那就讓線程直接去嘗試獲取鎖
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//
/** * 1. 若是w爲0,說明寫鎖的數量爲0,而此時又由於c不等於0,說明鎖被佔用,可是不是寫鎖,那麼此時鎖的狀態必定是讀鎖, * 既然是讀鎖狀態,那麼寫鎖此時來獲取鎖時,就確定失敗,所以當w等於0時,tryAcquire()方法返回false。 * 2. 若是w不爲0,說明此時鎖的狀態時寫鎖,接着進行current != getExclusiveOwnerThread()判斷,判斷持有鎖的線程是不是當前線程 * 若是不是當前線程,那麼tryAcquire()返回false;若是是當前線程,那麼就進行後面的邏輯。爲何是當前線程持有鎖,就還能執行後面的邏輯呢? * 由於讀寫鎖是支持重入的。 */
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 下面一行代碼是判斷,寫鎖的重入次數或不會超過最大限制,這個最大限制是:2的16次方減1
// 爲何是2的16次方減1呢?由於state的低16位存放的是寫鎖,所以寫鎖數量的最大值是2的16次方減1
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
/** * 1. writerShouldBlock()方法的做用是判斷當前線程是否應該阻塞,對於公平的寫鎖和非公平寫鎖的具體實現不同。 * 對於非公平寫鎖而言,直接返回false,由於非公平鎖獲取鎖以前不須要去判斷是否排隊 * 對於公平鎖寫鎖而言,它會判斷同步隊列中是否有人在排隊,有人排隊,就返回true,表示當前線程須要阻塞。無人排隊就返回false。 * * 2. 當writerShouldBlock()返回true時,表示當前線程還不能直接獲取鎖,所以tryAcquire()方法直接返回false。 * 當writerShouldBlock()返回false時,表示當前線程能夠嘗試去獲取鎖,所以會執行if判斷中後面的邏輯,即經過CAS方法嘗試去修改同步變量的值, * 若是修改同步變量成功,則表示當前線程獲取到了鎖,最終tryAcquire()方法會返回true。若是修改失敗,那麼tryAcquire()會返回false,表示獲取鎖失敗。 * */
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
複製代碼
exclusiveCount()
方法來計算寫鎖的數量,怎麼計算的呢?就是將state和0x0000FFFF
進行與運算
。writerShouldBlock()
方法判斷線程是否須要等待,若是須要等待,tryAcquire()方法就返回false,表示獲取鎖失敗,那麼就會回到AQS的acquire()方法中,後面的邏輯與排他鎖的邏輯同樣。若是不須要等待,就嘗試去修改state的值,若是修改爲功,就表示獲取鎖成功,不然失敗。current != getExclusiveOwnerThread()
判斷,判斷持有鎖的線程是不是當前線程。若是不是當前線程,那麼tryAcquire()返回false;若是是當前線程,那麼就進行後面的邏輯。爲何是當前線程持有鎖,就能執行後面的邏輯呢? 由於讀寫鎖是支持重入的。protected final boolean tryRelease(int releases) {
// 判斷是不是當前線程持有鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 將state的值減去releases
int nextc = getState() - releases;
// 調用exclusiveCount()方法,計算寫鎖的數量。若是寫鎖的數量爲0,表示寫鎖被徹底釋放,此時將AQS的exclusiveOwnerThread屬性置爲null
// 並返回free標識,表示寫鎖是否被徹底釋放
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
複製代碼
tryAcquireShared()
方法。該方法的源碼以下。protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// exclusiveCount(c)返回的是寫鎖的數量,若是它不爲0,說明寫鎖被佔用,若是此時佔用寫鎖的線程不是當前線程,就返回-1,表示獲取鎖失敗
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// r表示的是讀鎖的數量
int r = sharedCount(c);
/** * 在下面的代碼中進行了三個判斷: * 一、讀鎖是否應該排隊。若是沒有人排隊,就進行if後面的判斷。有人排隊,就不會進行if後面的判斷,而是最終調用fullTryAcquireShared()方法 * 二、讀鎖數量是否超過最大值。(最大數量爲2的16次方-1) * 三、嘗試修改同步變量的值 */
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 讀鎖數量爲0時,就將當前線程設置爲firstReader,firstReaderHoldCount=1
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 讀鎖數量不爲0且firstReader(第一次獲取讀的線程)爲當前線程,就將firstReaderHoldCount累加
firstReaderHoldCount++;
} else {
// 讀鎖數量不爲0,且第一個獲取到讀鎖的線程不是當前線程
// 下面這一段邏輯就是保存當前線程獲取讀鎖的次數,如何保存的呢?
// 經過ThreadLocal來實現的,readHolds就是一個ThreadLocal的實例
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
// 返回1表示獲取讀鎖成功
return 1;
}
// 當if中的三個判斷均不知足時,就會執行到這兒,調用fullTryAcquireShared()方法嘗試獲取鎖
return fullTryAcquireShared(current);
}
複製代碼
exclusiveCount()
方法來計算寫鎖的數量,若是寫鎖存在,再判斷持有寫鎖的線程是否是當前線程,若是不是當前線程,就表示寫鎖被其餘線程給佔用,此時當前線程不能獲取讀鎖。tryAcquireShared()方法返回-1,表示獲取讀鎖失敗。若是寫鎖不存在或者持有寫鎖的線程是當前線程,那麼就表示當前線程有機會獲取到讀鎖。ThreadLocal
。由於在讀寫鎖中提供了getReadLockCount()、getReadHoldCount()
等方法,這幾個方法的數據就來自這兒。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死循環,直到知足相應的條件纔會return退出,不然一直循環
for (;;) {
int c = getState();
// 鎖的狀態爲寫鎖時,持有鎖的線程不等於當期那線程,就說明當前線程獲取鎖失敗,返回-1
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
// 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)) {
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) {
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;
}
}
}
複製代碼
tryReleaseShared()
方法。該方法的源碼以下。protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
// 將修改同步變量的值(讀鎖狀態減去1<<16)
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;
}
}
複製代碼
1.
讀寫鎖不支持鎖升級
,支持鎖降級
。鎖升級指的是線程獲取到了讀鎖,在沒有釋放讀鎖的前提下,又獲取寫鎖。鎖降級指的是線程獲取到了寫鎖,在沒有釋放寫鎖的狀況下,又獲取讀鎖。爲何不支持鎖升級呢?能夠參考以下示例代碼。public void lockUpgrade(){
ReadWriteLock lock = new ReentrantReadWriteLock();
// 建立讀鎖
Lock readLock = lock.readLock();
// 建立寫鎖
Lock writeLock = lock.writeLock();
readLock.lock();
try{
// ...處理業務邏輯
writeLock.lock(); // 代碼①
}finally {
readLock.unlock();
}
}
複製代碼
2.
讀鎖不支持條件等待隊列。當調用ReadLock類的newCondition()方法時,會直接拋出異常。public Condition newCondition() {
throw new UnsupportedOperationException();
}
複製代碼
0x0000FFFF
進行與運算
,獲得的就是寫鎖的數量。