ReentrantLock
ReentrantLock實現了Lock接口,而ReentrantLock其實相當於一副空殼,它的主要功能就是控制構造出公平鎖還是非公平鎖,對鎖的相關操作細節都是由內部類Sync來完成。Sync繼承自AQS(AbstractQueuedSynchronizer),並再衍生出兩個內部類非公平鎖NofairSync和公平鎖FairSync。
通過ReentrantLock的的默認構造函數爲非公平鎖,也可通過含參構造函數來控制其構造公平鎖。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
兩種鎖的主要區別就在於lock操作上,FairSync的lock就是直接通過AQS的acquire函數來嘗試獲取同步狀態,但是NofairSync的lock會先嚐試性的直接去獲取同步狀態,如果失敗再通過acquire函數進行獲取。
//公平鎖FairSync的鎖操作
final void lock() {
acquire(1);
}
//非公平鎖NonfairSync的鎖操作
final void lock() {
//直接嘗試獲取同步狀態
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
AQS的acquire函數中又會調用子類的tryAcquire函數來嘗試獲取同步狀態,而兩種鎖在tryAcquire函數上又有不同。
//公平鎖FairSync的tryAcquire操作
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//區別在這裏,公平鎖會先判斷同步隊列有沒有其他節點在等待
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//在這裏可以看出可重入的思想,如果當前線程就是持有鎖的線程,則直接將狀態state + 1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//非公平鎖nonfairTryAcquire的鎖操作
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//非公平鎖不管同步隊列有沒有節點,而是直接嘗試獲取同步狀態
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//在這裏可以看出可重入的思想,如果當前線程就是持有鎖的線程,則直接將狀態state + 1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
ReentrantReadWriteLock
ReentrantReadWriteLock實現自ReadWriteLock(讀寫鎖)的接口。ReentrantReadWriteLock主要使用在數據的讀取操作遠多於寫操作的情況下,可以提高併發性。它具有如下特徵:
a. 維護了一對鎖ReadLock(讀鎖)和WriteLock(寫鎖)讀鎖在同一時刻允許多個線程持有,而寫鎖在被持有時,其他嘗試獲取讀鎖或者寫鎖的線程都會被阻塞。讀鎖的lock方法會直接調用sync的acquireShared方法,從而調用tryAcquireShared方法:
//讀鎖獲取同步狀態
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//如果當前寫鎖數量不爲0且獲取到同步狀態的線程不是當前線程,則直接返回-1。根據AQS的流程,此線程會被加入到同步隊列中。
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//獲取讀鎖數量
int r = sharedCount(c);
//按照當前鎖的模式(公平鎖/非公平鎖)判斷線程是否需要阻塞。
//如果不需要阻塞,且讀數量小於最大數量,嘗試CAS獲取同步狀態
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//如果讀鎖數量爲0,則當前線程爲第一個讀線程
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
}
//如果數量不爲0,但是當前線程就是第一個讀線程(說明是重入了),設置第一個讀線程的重入次數。
else if (firstReader == current) {
firstReaderHoldCount++;
}
//其他讀線程(非第一個)的重入次數設置。通過在每個線程中的內存空間保存HodlerCount類(用於記錄當前線程獲取鎖的次數),來獲取相應的次數
else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
//獲取同步狀態成功
return 1;
}
//獲取讀鎖失敗後,進入自旋繼續嘗試
return fullTryAcquireShared(current);
}
寫鎖的lock方法則會調用sync的tryAcquire方法:
//寫鎖獲取同步狀態
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//獲取狀態:讀 + 寫
int c = getState();
//通過狀態獲取寫數量
int w = exclusiveCount(c);
//同步狀態不爲0,說明有線程持有同步狀態
if (c != 0) {
//如果寫數量爲0(說明有線程在讀)或者 寫數量不爲0(說明沒有讀線程,只有寫線程)但是寫線程不是當前線程,則返回失敗
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//來到這裏說明是寫線程重入,則判斷寫數量是否超出最大值,超出拋異常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//當前線程成功獲取寫鎖,返回成功
setState(c + acquires);
return true;
}
/*
*同步狀態爲0,說明沒有線程在讀寫。則按照當前鎖的模式(公平鎖/非公平鎖)判斷線程是否需要阻塞。
*如果需要阻塞,或者不需要阻塞但是沒有成功獲取同步狀態,返回失敗
*/
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//說明成功獲取同步狀態,設置當前線程爲持有鎖線程,並返回成功
setExclusiveOwnerThread(current);
return true;
}
b. 還維護了一個Sync,所有的鎖操作都是由Sync完成的,也就是說讀鎖和寫鎖共同使用一個同步隊列和一個同步狀態state(在Sync裏面)。在ReentrantReadWriteLock的同步隊列中,將同步狀態state分成了高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;
//獲取讀數量,通過將state右移1位得到
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//獲取寫數量,通過將state和0xFFFF進行與操作來得到
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
寫操作雖然會阻塞其他的寫操作,但由於ReentrantReadWriteLock是可重入的鎖,所以寫數量會在該鎖被重入的時候用於記錄重入次數。
c. Sync內部含有子類NofairSync和FairSync,也就是說ReentrantReadWriteLock也是支持公平鎖和非公平鎖的。
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
ThreadLocal
在ReentrantReadWriteLock的readLock中,通過一個HoldCounter類來記錄當前線程的重入次數,然後將其存儲到readHolds中。而readHolds是在ReentrantReadWriteLock定義的一個內部靜態類ThreadLocalHoldCounter,繼承自ThreadLocal<HoldCounter>。
ThreadLocal是一種線程的本地存儲,它爲變量在每個線程中都創建了一個實例,存儲在共用的ThreadLocalMap中(以線程爲鍵,實例爲值)。ThreadLocal是線程共享的,但裏面存儲的變量卻不是共享的,因此ThreadLocal並不是用來解決多線程變量共享的問題,它只是用來方便用戶存儲和讀取的一種存儲結構。
鎖降級
鎖降級是指把持住當前擁有的寫鎖(進行數據寫入)的同時,再獲取到讀鎖進行數據讀取使用,然後再釋放寫鎖,最後釋放讀鎖。這樣做的目的是保證數據的可見性,防止本線程在數據完成寫操作後,再進行讀取前有其他線程修改了數據,從而讓本線程先獲取到讀鎖以保證其他線程無法加寫鎖,然後本線程再釋放寫鎖,讀取數據。
//這是Oracle官方的示例,體會一下。
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();
}
}
}