ReentrantLock簡介
ReentrantLock
是Lock
的子類茫打,它是可重入的鎖累贤,即已經(jīng)獲取鎖的線程可以重復獲取鎖狱杰,同時它還支持公平鎖和非公平鎖。如果保證先請求資源的線程總是先獲得鎖看疙,那么就是公平鎖豆拨,否則是非公平鎖。非公平鎖的效率要比公平鎖高能庆,不過非公平鎖可能存在線程饑餓問題施禾。ReentrantLock
的使用很簡單,示例如下
public class Sequence {
private int value;
Lock lock = new ReentrantLock();
public int getNext() {
lock.lock();
try {
int a = value ++;
return a;
}finally {
//釋放鎖的代碼建議防止finally塊當中
lock.unlock();
}
}
public static void main(String[] args) {
final Sequence s = new Sequence();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() +
" " + s.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() +
" " + s.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
ReentrantLock源碼解析
ReentrantLock
內(nèi)部維護著Sync
類型的成員變量搁胆,Sync
是定義在ReentrantLock
中的靜態(tài)內(nèi)部類弥搞,同時Sync
是AbstractQueuedSynchronizer
的子類邮绿。ReentrantLock
鎖功能的實現(xiàn)就是借助AQS
來實現(xiàn)的。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
......
}
}
首先攀例,我們先來看看ReentrantLock
的構(gòu)造方法船逮,ReentrantLock
提供了兩個構(gòu)造方法,一個無參的粤铭,另一個帶一個boolean
類型的參數(shù)挖胃。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
構(gòu)造器的工作就是在實例化sync
成員變量,分有FairSync
和NonFairSync
兩種梆惯,這兩個類都是Sync
的子類酱鸭,即這兩個類都是AQS
的子類。通過FairSync
讓ReentrantLock
變成公平鎖垛吗,NonFairSync
讓ReentrantLock
變成非公平鎖凹髓。
如果能讓先競爭資源的線程獲取鎖,這樣的鎖稱為公平鎖怯屉,否則稱為非公平鎖蔚舀。公平鎖的效率通常比非公平鎖的效率要低很多,但是非公平鎖可能會讓線程產(chǎn)生"饑餓等待"問題锨络。下面我們重點來看看非公平鎖是如何實現(xiàn)的赌躺。
加鎖實現(xiàn)
ReentrantLock
一般通過lock()方法實現(xiàn)加鎖,而lock方法的內(nèi)部會委托給sync
的lock
方法羡儿,而sync
是個隊列同步器(AQS
)寿谴,它又分公平和非公平兩種實現(xiàn)。下面來看看非公平鎖的實現(xiàn)失受,即NonfairSync
的lock
方法
final void lock() {
// 通過CAS嘗試獲取同步資源,即將同步變量由0置為1
if (compareAndSetState(0, 1))
// 成功獲取同步資源之后咏瑟,設置當前線程拂到,標明當前線程持有鎖
setExclusiveOwnerThread(Thread.currentThread());
else
// acquire方法為AbstractQueuedSynchronizer的模板方法
acquire(1);
}
acquire
的實現(xiàn)中會先調(diào)用tryAcquire
嘗試獲取資源,獲取失敗就自旋阻塞等待码泞。NonfairSync
和FairSync
都重寫了tryAcquire
方法兄旬,實現(xiàn)代碼如下
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 獲取同步狀態(tài)
int c = getState();
//如果同步狀態(tài)為0,表明當前鎖沒有被任何線程鎖持有
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 實現(xiàn)鎖的可重入(判斷當前持有鎖的線程是否等于當前線程)
else if (current == getExclusiveOwnerThread()) {
// 同步狀態(tài)+1余寥, 這里acquires的實際值為1
int nextc = c + acquires;
// 判斷nextc是否越界领铐,偉大的程序員就是厲害!
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
當已經(jīng)獲取鎖的線程再次嘗試獲取鎖宋舷,即鎖重入绪撵,重入幾次同步狀態(tài)(state)的值就加幾,釋放鎖的時候要將state減為0時才能成功釋放鎖祝蝠。
公平鎖獲取的代碼幾乎和非公平鎖一樣音诈,區(qū)別就是公平鎖在獲取鎖之前要先判斷當前線程之前是否還有其他線程(來得比當前線程早)等待獲取鎖幻碱,如果當前線程還有其他線程那么當前線程需要繼續(xù)等待,來得早的線程先獲得鎖细溅。
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;
}
}
......
}
hasQueuedPredecessors()
是AQS
中的方法褥傍,它通過判斷內(nèi)部的FIFO隊列當前線程所在的節(jié)點是否有前驅(qū)節(jié)點來實現(xiàn),如果存在前驅(qū)節(jié)點則返回true
喇聊。由于是FIFO隊列恍风,所以前驅(qū)節(jié)點要比當前節(jié)點早入隊列。
釋放鎖實現(xiàn)
ReentrantLock
通過unlock()
方法來釋放鎖誓篱,unlock()
會調(diào)用sync
的release方法朋贬。release(int arg)
同樣也是AQS
的模板方法,它的內(nèi)部會調(diào)用tryRelease
方法嘗試釋放同步資源燕鸽。NonfairSync
和FairSync
共用同個tryRelease
方法(定義在Sync
內(nèi)部類中)兄世。
protected final boolean tryRelease(int releases) {
// 當前狀態(tài)-releases (state-1)
int c = getState() - releases;
// 如果當前線程不是持有鎖的線程,拋出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 只有state等于0啊研,才表示釋放鎖成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
我們可以看到tryRelease
沒有使用什么同步機制御滩,這是因為能成功獲取鎖的線程只有一個,所以釋放鎖的時候党远,不會存在多線程競爭的情況削解。