讀鎖不可以升級,寫鎖可以降級?
讀鎖是可並行的,寫鎖是串行的,那麼如果多個讀鎖並行執行,遇到升級語句,就會出現死鎖,比如t1要升級,那麼就要等t2釋放鎖,而t2正好也在當t1釋放鎖。
加鎖:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
// 獲取當前線程
Thread current = Thread.currentThread();
// 線程計數
int c = getState();
// 獲取獨佔鎖,就是寫鎖
int w = exclusiveCount(c);
// 線程計數!=0,表示重入;這裏有兩種:讀鎖,寫鎖
if (c != 0) {
// 如果是讀鎖,或者當前線程並非加鎖線程,返回false,就會進入acquireQueued(addWaiter(Node.EXCLUSIVE), arg))獲取鎖
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 判斷寫鎖的可重入次數是否超過MAX_COUNT,超過拋異常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 到這裏,確定是寫鎖且當前線程已持有鎖,重入鎖+1
setState(c + acquires);
return true;
}
// 走到這裏,表示c=0,即沒有寫鎖,也沒有讀鎖
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
final boolean writerShouldBlock() {
// 這個方法就是上一篇鎖講過的,查詢是否有線程等待,有就排隊
return hasQueuedPredecessors();
}
if (writerShouldBlock() ||
// 設置線程計數
!compareAndSetState(c, c + acquires))
return false;
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// 然後進入這個方法,上一篇講過
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這裏就是,隊列裏排隊獲取鎖,和reentrantLock一樣。
讀鎖:
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 判斷是否獨佔鎖,是否當前線程;這裏如果出現獨佔鎖,那麼該線程就返回-1,去排隊
// 爲什麼呢?因爲在讀鎖加鎖的時候,也可能出現寫鎖進來,如果寫鎖進來了,寫鎖是排他鎖,獨佔一把鎖,那麼讀鎖也要去排隊
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 非獨佔鎖(寫鎖),獲取計數
int r = sharedCount(c);
// 沒有線程等待,
if (!readerShouldBlock() &&
// 且讀鎖計數小於最大值,
r < MAX_COUNT &&
// 且加鎖成功
compareAndSetState(c, c + SHARED_UNIT)) {
// 讀鎖計數爲0,沒有讀鎖,第一個線程進來
if (r == 0) {
// 就給第一個線程對象賦值,
firstReader = current;
firstReaderHoldCount = 1;
// 如果說第一個線程對象等於當前線程對象,就是重入鎖
} else if (firstReader == current) {
// 那麼第一個線程內部計數+1
firstReaderHoldCount++;
// 如果以上條件都否定,即不是第一個線程,也不是重入鎖
} else {
// 第一次進來,重入鎖計數對象 = null
HoldCounter rh = cachedHoldCounter;
// 當前緩存中還沒有,或者是第二次進來,rh不會空,那麼判斷rh的線程id是否和當前線程id相同,不同則表示其他線程進入
if (rh == null || rh.tid != getThreadId(current))
// 拿到緩存的重入鎖對象:如果是同一個線程進入,就返回那個線程的緩存計數對象,如果是其他線程,就會初始化一個返回
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
// 緩存對象計數+1
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
當前線程可重入鎖的數量是內部是線程變量實現;
它自己本身保存了一份ThreadLocalMap對象,用於保存重入鎖的線程
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 有當前線程
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 拿不到就初始化一個
return setInitialValue();
}
假設第三個線程:
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 判斷是否獨佔鎖,是否當前線程;這裏如果出現獨佔鎖,那麼該線程就返回-1,去排隊
// 爲什麼呢?因爲在讀鎖加鎖的時候,也可能出現寫鎖進來,如果寫鎖進來了,寫鎖是排他鎖,獨佔一把鎖,那麼讀鎖也要去排隊
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 非獨佔鎖(寫鎖),獲取計數
int r = sharedCount(c);
// 沒有線程等待,
if (!readerShouldBlock() &&
// 且讀鎖計數小於最大值,
r < MAX_COUNT &&
// 且加鎖成功
compareAndSetState(c, c + SHARED_UNIT)) {
// 讀鎖計數爲0,沒有讀鎖,第一個線程進來
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// 第三次rh對象不爲空
HoldCounter rh = cachedHoldCounter;
// rh != null,但tid不同 判斷爲true
if (rh == null || rh.tid != getThreadId(current))
// 從緩存對象中獲取到緩存對象;存在返回,不存在初始化
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
// 緩存對象計數+1,這時緩存對象中第三個的計數是1
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}