1、ReentrantLock的特性
ReentrantLock是Java併發包中提供的一個可重入的互斥鎖。ReentrantLock和synchronized在基本用法,行爲語義上都是類似的,同樣都具有可重入性。只不過相比原生的Synchronized,ReentrantLock增加了一些高級的擴展功能,比如它可以實現公平鎖,同時也可以綁定多個Conditon。
可重入性:
是指可以支持一個線程對鎖的重複獲取與釋放。原生的synchronized就具有可重入性,一個用synchronized修飾的遞歸方法,當線程在執行期間,它是可以反覆獲取到鎖的,而不會出現自己把自己鎖死的情況。ReentrantLock也是如此,在調用lock()方法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞,並且lock和unlock的調用次數必須相等纔會釋放鎖。
公平鎖/非公平鎖:
公平鎖:是指線程獲取鎖的順序和調用lock的順序一樣,FIFO(先進先出)方式獲取和釋放鎖。
非公平鎖:線程獲取鎖的順序和調用lock的順序無關,能否獲取鎖取決於調用時機。
synchronized是非公平鎖,ReentrantLock默認也是非公平的,但是可以通過帶boolean參數的構造方法指定使用公平鎖,但非公平鎖的性能一般要優於公平鎖。
2、ReentrantLock實現
ReentrantLock的所有鎖相關操作都是通過Sync類實現,Sync繼承於AbstractQueuedSynchronizer同步隊列,並實現一些通用的接口實現。
NonfairSync繼承於Sync,實現了非公平的方式獲取鎖;
FairSync繼承於Sync,實現了公平的方式獲取鎖;
ReentrantLock類實現結構如下:
2.1、Sync實現
//sync繼承於AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//抽象方法,需子類實現
abstract void lock();
//實現了非公平方式獲取鎖
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//獲取當前鎖狀態,此狀態c>0表示有線程獲取到鎖,重入的次數爲c
int c = getState();
//無線程獲取鎖?
if (c == 0) {
//CAS方式獲取鎖,成功則設置獲取獨佔鎖的線程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//獲得鎖的線程就是當前線程?則獲取次數加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;
}
//釋放鎖
protected final boolean tryRelease(int releases) {
//計算需要更新的狀態值
int c = getState() - releases;
//若當前線程未獲得鎖,則拋出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//若將要更新的狀態值爲0,表示當前線程最後一次釋放鎖;
//此時鎖纔會真正的釋放,其他線程才能獲取;
//否則表示當前線程獲取鎖的次數大於釋放鎖的次數
if (c == 0) {
free = true;
//清除記錄的獨佔鎖的線程
setExclusiveOwnerThread(null);
}
//更新狀態
setState(c);
return free;
}
//判斷當前線程釋放獲取獨佔鎖
protected final boolean isHeldExclusively() {
//當前線程爲記錄的獲取獨佔鎖的線程,則表示當前線程獲得獨佔鎖
return getExclusiveOwnerThread() == Thread.currentThread();
}
//創建新的條件隊列
final ConditionObject newCondition() {
return new ConditionObject();
}
//獲取獲取獨佔鎖的線程對象
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//獲取當前線程獲取鎖的次數
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//判斷鎖是否被佔用
final boolean isLocked() {
return getState() != 0;
}
}
2.2、非公平鎖NonfairSync的實現
//非公平鎖NonfairSync的實現,其繼承於Sync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//lock接口實現;
//首先通過CAS試圖獲取鎖,獲取成功則設置鎖的Owner;
//否則調用acquire獲取鎖,acquire又或調用tryAcquire獲取鎖,
//而tryAcquire是通過非公平的方式獲取鎖。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//非公平方式獲取鎖
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
2.3、公平鎖FairSync的實現
//公平鎖FairSync 的實現,其繼承於Sync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//lock接口實現,自己調用acquire獲取鎖;
//acquire又會調用tryAcquire獲取鎖,而tryAcquire是通過公平(FIFO)
//的方式獲取鎖。
final void lock() {
acquire(1);
}
//公平的方式獲取鎖
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//獲取當前鎖狀態,此狀態c>0表示有線程獲取到鎖,重入的次數爲c
int c = getState();
//無線程獲取鎖?
if (c == 0) {
//當前節點無前驅節點並且當前線程CAS更新狀態成功;、
//表示當前線程公平的獲取到鎖
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//獲得鎖的線程就是當前線程?則獲取次數加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;
}
}
2.4、鎖可重入的實現原理
通過以上源碼分析可知,當某個節點獲取到鎖時,會通過setExclusiveOwnerThread()方法記錄獲取獨佔鎖的線程Thread;當某個線程獲取鎖時,當鎖已被佔用,會判斷佔用鎖的線程是否爲當前線程;是則直接更新鎖狀態,表示獲取到鎖;否則獲取鎖失敗。
2.5、鎖公平與非公平的實現原理
公平鎖是通過FairSync實現的,其在tryAcquire獲取鎖時,會判斷同步隊列中當前節點是否有前驅節點;有前驅節點,則獲取鎖失敗,進入同步隊列,等待獲取鎖;無前驅節點時,表示當前節點是同步隊列中等待鎖時間最長的節點,則當前節點優先獲取鎖資源。
非公平鎖是通過NonfairSync實現的,其在lock及tryAcquire時,會先通過CAS的方式嘗試獲取鎖,獲取失敗纔會進入同步隊列等待。這就導致當某個線程剛釋放鎖,而同步隊列中被unpark的頭節點還未CAS獲取到鎖的時間間隙,當前線程先於同步隊列頭結點通過CAS獲取鎖。使得某些線程會等待很長時間纔會獲得鎖,這是非公平性的。
ReentrantLock的構造方法如下:
//無參構造方法,會通過非公平的方式實現鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//帶參數的構造方法;
//fire=true:會通過公平的方式構造鎖;
//fire=false:會通過非公平的方式構造鎖;
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}