重入鎖
(1)重進入:
1.定義:重進入是指任意線程在獲取到鎖之后,再次獲取該鎖而不會被該鎖所阻塞。關聯一個線程持有者+計數器,重入意味着鎖操作的顆粒度為“線程”。
2.需要解決兩個問題:
線程再次獲取鎖:鎖需要識別獲取鎖的現場是否為當前占據鎖的線程,如果是,則再次成功獲取;
鎖的最終釋放:線程重復n次獲取鎖,隨后在第n次釋放該鎖后,其他線程能夠獲取該鎖。要求對鎖對於獲取進行次數的自增,計數器對當前鎖被重復獲取的次數進行統計,當鎖被釋放的時候,計數器自減,當計數器值為0時,表示鎖成功釋放。
3.重入鎖實現重入性:每個鎖關聯一個線程持有者和計數器,當計數器為0時表示該鎖沒有被任何線程持有,那么任何線程都可能獲得該鎖而調用相應的方法;當某一線程請求成功后,JVM會記下鎖的持有線程,並且將計數器置為1;此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增;當線程退出同步代碼塊時,計數器會遞減,如果計數器為0,則釋放該鎖
(2)ReentrantLock是的非公平類中通過組合自定義同步器來實現鎖的獲取與釋放。
1 /** 2 * Sync中的nonfairTryAcquire()方法實現 3 * 這個跟公平類中的實現主要區別在於不會判斷當前線程是否是等待時間最長的線程 4 **/ 5 final boolean nonfairTryAcquire(int acquires) { 6 final Thread current = Thread.currentThread(); 7 int c = getState(); 8 if (c == 0) { 9 // 跟FairSync中的主要區別,不會判斷hasQueuedPredecessors() 10 if (compareAndSetState(0, acquires)) { 11 setExclusiveOwnerThread(current); 12 return true; 13 } 14 } 15 else if (current == getExclusiveOwnerThread()) { 16 int nextc = c + acquires; 17 if (nextc < 0) // overflow 18 throw new Error("Maximum lock count exceeded"); 19 setState(nextc); 20 return true; 21 } 22 return false; 23 }
nonfairTryAcquire()方法中,增加了再次獲取同步狀態的處理邏輯,通過判斷當前線程是否為獲取鎖的線程來決定獲取操作是否成功,如果是獲取鎖的線程再次請求,則將同步狀態值進行增加並返回true,表示獲取同步狀態成功。
成功獲取鎖的現場再次獲取鎖,只是增加了同步狀態值,要求ReentrantLock在釋放同步狀態時減少同步狀態值。
1 /** 2 * Sync中tryRelease() 3 **/ 4 protected final boolean tryRelease(int releases) { 5 // 修改當前鎖的狀態 6 // 如果一個線程遞歸獲取了該鎖(也就是state != 1), 那么c可能不等0 7 // 如果沒有線程遞歸獲取該鎖,則c == 0 8 int c = getState() - releases; 9 10 // 如果鎖的占有線程不等於當前正在執行釋放操作的線程,則拋出異常 11 if (Thread.currentThread() != getExclusiveOwnerThread()) 12 throw new IllegalMonitorStateException(); 13 boolean free = false; 14 // c == 0,表示當前線程釋放鎖成功,同時表示遞歸獲取了該鎖的線程已經執行完畢 15 // 則設置當前鎖狀態為free,同時設置鎖的當前線程為null,可以讓其他線程來獲取 16 // 同時也說明,如果c != 0,則表示線程遞歸占用了鎖資源, 17 // 所以鎖的當前占用線程依然是當前釋放鎖的線程(實際沒有釋放) 18 if (c == 0) { 19 free = true; 20 setExclusiveOwnerThread(null); 21 } 22 // 重新設置鎖的占有數 23 setState(c); 24 return free; 25 }
如果該鎖被獲取n次,則前(n-1)次tryRelease(int releases)方法必須返回false,而只有同步狀態完全釋放了,才返回true,該方法將同步狀態是否為0作為最終釋放的條件,當同步狀態為0時,將占有線程設置為null,並返回true,表示釋放成功。
對於公平鎖而言
1 /** 2 * FairSync中tryAcquire()的實現 3 * 返回 4 * true: 獲取鎖成功 5 * false: 獲取鎖不成功 6 **/ 7 protected final boolean tryAcquire(int acquires) { 8 // 獲取當前線程 9 final Thread current = Thread.currentThread(); 10 // 獲取鎖資源的狀態 11 // 0: 說明當前鎖可立即獲取,在此種狀態下(又是公平鎖) 12 // >0並且當前線程與持有鎖資源的線程是同一個線程則state + 1並返回true 13 // >0並且占有鎖資源的不是當前線程,則返回false表示獲取不成功 14 int c = getState(); 15 if (c == 0) { 16 // 在鎖可以立即獲取的情況下 17 // 首先判斷線程是否是剛剛釋放鎖資源的頭節點的下一個節點(線程的等待先后順序) 18 // 如果是等待時間最長的才會馬上獲取到鎖資源,否則不會(這也是公平與不公平的主要區別所在) 19 if (!hasQueuedPredecessors() && 20 compareAndSetState(0, acquires)) { 21 setExclusiveOwnerThread(current); 22 return true; 23 } 24 } 25 else if (current == getExclusiveOwnerThread()) { //線程可以遞歸獲取鎖 26 int nextc = c + acquires; 27 // 超過int上限值拋出錯誤 28 if (nextc < 0) 29 throw new Error("Maximum lock count exceeded"); 30 setState(nextc); 31 return true; 32 } 33 return false; 34 }
與非公平唯一的區別是判斷條件中多了hasQueuedPredecessors()方法,即加入了同步隊列中當前節點是否有前驅節點的判斷,如果該方法返回了true,則表示有線程比當前線程更早地請求獲取鎖,所以需要等待前驅線程獲取並釋放鎖后才能繼續獲取該鎖。
但是非公平鎖是默認實現:非公平性鎖可能使線程“飢餓”,但是極少的線程切換,可以保證其更大的吞吐量。而公平性鎖,保證了鎖的獲取按照FIFO原則,代價是進行大量的線程切換。
(3)synchronized可重入性
同一線程在調用自己類中其他synchronized方法/塊或調用父類的synchronized方法/塊都不會阻礙該線程的執行,就是說同一線程對同一個對象鎖是可重入的,而且同一個線程可以獲取同一把鎖多次,也就是可以多次重入。