ReentrantLock , 可重入鎖, 和 synchronized 有着相同的內存語義
比較下這倆者之間的相同以及區別
相同點:
倆者有着相同的內存語義,
1、當線程獲取鎖時,JMM會把線程對應的本地內存置爲無效,然後臨界區的代碼從主存中讀入共享變量到工作內存。
2、 當線程釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存中。
區別在於,ReentrantLock是應用層面, 是通過volatile來實現同樣的語義
不同點:
synchronized是jvm內置鎖,ReentrantLock是應用層面實現的鎖,所以ReentrantLock在運用方面會更靈活
1、ReentrantLock提供了 鎖查詢(tryLock)、鎖等待 等方法,可以有效的防止死鎖,synchronized只能阻塞了
2、ReentrantLock提供了可以中斷的加鎖機制(lockInterruptibly),synchronized不響應中斷
3、ReentrantLock提供了公平鎖模式,synchronized只有非公平鎖
4、ReentrantLock支持更加靈活的同步代碼塊,但是這也導致了一定的風險,一定要保證在finally中處理鎖釋放,synchronized則沒有這方面擔憂,是由jvm控制
性能方面:
這一塊很多人有誤解,認爲ReentrantLock一定比synchronized要優秀,實際上在Java1.6之前確實是這樣,不過從Java1.6開始,synchronized做了很多性能優化,已經不比ReentrantLock差.
而且隨着jvm的發展和完善,synchronized的性能會越來越好,畢竟它是jvm內置的鎖,在擴展優化方面有着優勢.
java 1.8版的ConcurrentHashMap就是用cas和synchronized完成的
如何選擇
1、大部分情況下,如果ReentrantLock和synchronized都能滿足需求,優先選擇synchronized.
2、如果有鎖嵌套的情況下,可以考慮使用ReentrantLock的鎖查詢、鎖等待等機制,防止死鎖
3、特殊場景需要用到公平鎖,考慮使用ReentrantLock
4、要使用多個條件隊列的情況下,考慮使用ReentrantLock的condition
接下來我們分析下ReentrantLock的源碼,先看下部分類圖結構
從圖中可以看出ReentrantLock是用內部類(Sync)繼承了AQS, 然後在這個基礎上實現了倆個子類, NonfairSync(非公平鎖),FairSync(公平鎖)
因爲是繼承了AQS,所以ReentrantLock只需要在這個基礎上完善tryAcquire、tryRelease方法即可,接下來以非公平鎖爲例,看下源碼部分
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//非公平鎖實現具體的tryAcquire邏輯,因爲是獨佔鎖,這裏的acquires傳入1
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//獲取同步狀態state,如果爲0說明此時沒有競爭,可以嘗試獲取同步狀態
int c = getState();
if (c == 0) {
//通過cas 將狀態設置爲tryAcquire
if (compareAndSetState(0, acquires)) {
//因爲是獨佔鎖,所以成功後,記錄下獲取同步狀態的線程
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不爲0,說明已經有線程獲取了同步狀態,檢查是否是當前線程獲取
else if (current == getExclusiveOwnerThread()) {
//如果是的話,則獲取同步狀態,並且state+1,表示重入的次數
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
以上是嘗試獲取同步狀態的方法,成功返回true,失敗返回false, 如果返回false,AQS將會將該線程放入鎖同步隊列中阻塞等待(詳情看這裏AbstractQueuedSynchronizer解析)
//釋放同步狀態,因爲是獨佔鎖這裏的releases爲1
//因爲是可重入鎖,這裏state表示重入次數,釋放一次state-1
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//如果獲取同步狀態的線程不是當前線程,則拋IllegalMonitorStateException
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果state爲0說明同步狀態已完全釋放
if (c == 0) {
free = true;
//在這裏將之前記錄獲取同步狀態的線程清除
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
上面是嘗試釋放同步狀態的方法,成功返回true,失敗返回false,如果爲true,AQS將會喚醒同步鎖隊列中的線程,繼續競爭同步狀態
ReentrantLock支持公平鎖,看下這部分實現邏輯
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
//該方法在類FairSync下,邏輯和非公平鎖基本一致
//唯一的區別是在競爭同步狀態之前,需要判斷隊列中是否有其它正在等待的線程
//如果有,那麼只能放棄競爭老老實實的進入隊列排在最後面
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//重點在這裏,多了一個hasQueuedPredecessors方法
//判斷同步鎖隊列中是否還有其他正在等待的節點
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;
}
}
}