Java併發程式設計(八)ReentrantLock
一、ReentrantLock簡介
ReentrantLock可重入鎖,全名java.util.concurrent.locks.ReentrantLock,相當於是個最基礎版本的Lock的實現,針對公平鎖和非公平鎖,ReentrantLock都有實現。
二、ReentrantLock特性
1、可輪詢鎖和定時鎖
可以通過呼叫trylock方法,查詢鎖的狀態,如果鎖已經被其他執行緒持有,則不會一直阻塞下去,避免死鎖。
2、公平鎖和非公平鎖
公平鎖是按照發出請求的順序獲取鎖,不允許插隊;而非公平鎖允許插隊,二者各有優缺點,ReentradntLock都有實現。
公平鎖的作用就是嚴格按照執行緒啟動的順序來執行的,不允許其他執行緒插隊執行的;而非公平鎖是允許插隊的,但由於可以減少執行緒恢復和掛起操作,總體執行效率更高。
3、可中斷鎖
lockInterruptibly方法能夠在獲取鎖的同時保持對中斷的響應,因此無需建立其它型別的不可中斷阻塞操作。
三、ReentrantLock原理
ReentrantLock的架構相對簡單,主要包括一個Sync的內部抽象類以及Sync抽象類的兩個實現類。Sync繼承自AQS,Sync的兩個實現類分別是NonfairSync和FairSync,即非公平鎖和公平鎖。下面分別針對非公平鎖和公平鎖講一下ReentrantLock的原理。
1、非公平鎖的lock
非公平鎖lock方法原始碼如下:
// 非公平鎖的lock方法 // java.util.concurrent.locks.ReentrantLock.NonfairSync#lock final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
呼叫過程整理,總共分為兩步:
第一步,直接嘗試CAS搶佔鎖,如果搶佔成功,返回成功,如果搶佔失敗,則進行第二步;
第二步,呼叫acquire方法,這個方法內做了兩件事,第一件事,先呼叫tryAcquire方法嘗試搶佔鎖,如果失敗則第二件事,即先呼叫addWaiter方法將執行緒節點加入同步佇列,然後進入acquireQueued方法嘗試搶佔鎖。
下面分別講這三個方法,tryAcquire、addWaiter和acquireQueued。
tryAcquire方法內,先判斷當前鎖是否已被搶佔,如果未被搶佔,那麼就CAS嘗試搶佔鎖,如果搶佔成功,那麼就設定當前執行緒為已搶佔鎖的執行緒;如果鎖還未被搶佔,那麼判斷當前執行緒是不是已獲得鎖的執行緒,如果是,那麼就更新同步狀態位,否則搶佔失敗。tryAcquire方法原始碼如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
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;
}
addWaiter方法內,先判斷當前佇列是否為空,如果為空,則直接CAS設定當前節點為頭結點;如果佇列不為空,則CAS設定當前節點為尾節點。addWaiter方法原始碼如下:
// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
acquireQueued方法內,在一個死迴圈內,不斷判斷當前節點的前驅節點是否是頭結點,如果是,則表示當前節點可搶佔鎖,會呼叫tryAcquire方法嘗試搶佔鎖。acquireQueued方法原始碼如下:
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2、公平鎖的lock
公平鎖與非公平鎖的實現有兩個差別,一是不會直接搶佔,而是隻呼叫acquire方法;二是tryAcquire方法實現有差別,上面講到非公平鎖在搶佔鎖時,如果當前無執行緒佔鎖,那麼就直接CAS搶佔鎖,而非公平鎖需要先判斷當前節點是否無前驅節點,如果沒有前驅節點才可以搶佔鎖,這是為了保證FIFO。原始碼如下:
// 公平鎖lock,其中,acquire方法與非公平鎖是同一個
final void lock() {
acquire(1);
}
// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
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;
}
3、unlock
unlock是不區分公平鎖和非公平鎖的,unlock的時候做兩件事,一是同步狀態更新並setExclusiveOwnerThread(null)設定當前持有鎖的執行緒為null,二是喚醒佇列中的後續節點搶佔,unlock鎖原始碼如下:
public void unlock() {
sync.release(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.release()
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
注:
本文中所有原始碼均為jdk1.7.0_79版本。