1、什麼是可重入鎖?
可重入鎖就是對於已經獲得鎖的線程,可以重複的多次的獲得該鎖。
而不可重入的鎖在線程獲得該鎖後,該線程如果再次請求獲得該鎖,就會在調用tryAquires()的時候返回false,從而阻塞自己。
2、可重入鎖的實現原理?
要實現可重入鎖的關鍵有兩個,一個怎麼識別當前請求鎖的線程是不是已經獲取鎖的線程,另一個因爲在一個線程重複的獲取了n次鎖以後,必須要釋放n次鎖才能完全釋放鎖,這怎麼實現?
對於第一個問題,因爲可重入鎖是獨佔鎖,所以只要比較當前線程是不是獨佔鎖的線程,如果是則可以再次加鎖,如果不是則返回false。
對於第二個問題,需要一個與鎖關聯的計數器,來對加鎖的重數進行計數,每次獲得鎖都應該讓計數器自增,而每次釋放鎖都應該讓計數器自減,當計數器爲0時,表示鎖已經成功釋放。
源碼分析:
那麼我們來看看源碼:
public class ReentrantLock implements Lock, java.io.Serializable
▶從繼承關係上,可見ReentrantLock是實現了Lock接口,因此Lock接口的功能ReentrantLock也有Lock接口的可中斷等待鎖的線程,可以實現Condition等功能。
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
//重寫nonFairAquire
final boolean nonfairTryAcquire(int acquires) {
//省略此處,在公平鎖和非公平鎖的實現中會講到
}
//重寫tryRelease
protected final boolean tryRelease(int releases) {
//省略此處
}
從實現上可以看出,ReentrantLock是通過組合自定義同步器來實現鎖的獲取與釋放,在ReentrantLock自定義了內部類sync,內部類sync繼承了AbstractQueuedSynchronizor,並重寫了nonfairTryAquire()和tryRelease()方法來獲得鎖和釋放鎖。
▶然後再看ReentrantLock的構造方法:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以看出,ReentrantLock默認實現非公平鎖,如果傳入表示公平的boolean變量fair,那麼fair爲true的時候,實現公平鎖,fair爲false的時候,實現非公平鎖。而在ReentrantLock中實現公平鎖和非公平鎖都是通過實現一個靜態內部類來實現了,該靜態內部類繼承了sync靜態內部類。源碼如下所示:
公平鎖靜態內部類
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;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
因爲在sync內部類中沒有重寫公平鎖的獲取鎖的方法,因此在公平鎖的內部類中重寫tryAquire()方法,實現公平鎖的獲取。
通過tryAquire()方法我們可以看出ReentrantLock的實現流程:首先獲取鎖的同步狀態值,這個值是被volatile修飾的,這樣可以保證這個值在多線程之間是可見的,如果鎖沒有被佔用(c==0)那麼讓隊列頭的線程來獲取鎖,如果鎖已經被佔用,因爲ReentrantLock是排他鎖,因此可以把當前線程和獨佔的線程比較,如果當先線程就是獨佔鎖的線程,那麼可以獲取鎖,如果不是,則獲取鎖失敗。
非公平鎖靜態內部類
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
因爲在sync內部類中已經重寫了nonFairAquire()方法,因此直接調用nonFairAquire()方法即可。
此外因爲在sync內部類中已經重寫了tryRelease()方法,因此在這兩個子類中都沒有重寫這個方法,需要的時候直接調用即可。
3、細節:
①synchronized關鍵字也是一種可重入鎖,比如synchronized修飾的遞歸方法,在方法執行時,執行的線程在獲得了鎖以後仍然能夠多次的獲得鎖,而不會因爲鎖之前已經被佔有被阻塞線程本身。
②重入鎖與synchronized的區別:
○性能上:
▶重入鎖是顯式的重入,而synchronized是隱式的重入。也就是說重入鎖是用戶自己添加和釋放,由JDK實現,而 synchronized由操作系統來添加和釋放鎖,由JVM實現。因此便利性是synchronized優於重入鎖。
▶鎖的細粒度和靈活度:重入鎖優於synchronized
▶synchronized在優化前,性能遠不如重入鎖,但是在synchronized引入偏向鎖後、輕量級鎖(自旋鎖)後,二者的性能區別就不大了,在兩者都可用的情況下,官方甚至建議使用synchronized。
○功能上:
▶因爲重入鎖實現了lock接口,因此提供了Condition類,可以實現分組喚醒需要喚醒的線程,而不是像synchronized一樣要麼隨機喚醒,要麼全部喚醒。
▶重入鎖可以實現公平鎖,但是默認的是實現非公平鎖;而synchronized只能實現非公平鎖;(關於公平鎖與非公平鎖,參見:點擊打開鏈接)