JDK5之前多線程的鎖都是使用synchronized ,JDK 5中的鎖是接口java.util.concurrent.locks.Lock。另外java.util.concurrent.locks.ReadWriteLock提供了一對可供讀寫併發的鎖。ReentrantLock是java.util.concurrent.locks中的一個可重入鎖類。在高競爭條件下有更好的性能,且可以中斷。深入剖析ReentrantLock的源碼有助於我們瞭解線程調度,鎖實現,中斷,信號觸發等底層機制,實現更好的併發程序。
先來看ReentrantLock最常用的代碼lock
public void lock() {
sync.lock();
}
代碼很簡單,直接調用成員變量sync的lock方法
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
Sync 是ReentrantLock的抽象靜態內部類繼承了AbstractQueuedSynchronizer ,後面會看到很多操作其實都是通過AbstractQueuedSynchronizer 來實現的,AbstractQueuedSynchronizer 是一個很重要的類型,concurrent 包裏很多實現都依賴他。完整的設計思想可以參考 http://gee.cs.oswego.edu/dl/papers/aqs.pdf 。FairSync和NonFairSync則是具體的子類,分別對應了公平鎖和非公平鎖。其實這兩個都差不多,瞭解其中一個去看另一個其實差不多。實際中公平鎖吞吐量比非公平鎖小很多,所以以下分析以非公平鎖爲例。 在說具體的實現前不得不說AbstractQueuedSynchronizer, 最重要的兩個數據成員當前鎖狀態和等待鏈表都是由它來實現的。
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
state記錄了當前鎖被鎖定的次數。如果爲0則未被鎖定。加鎖通過更改狀態實現,而更改狀態主要由函數compareAndSetState實現,調用cas原語以保證操作的原子性。Node是靜態內部類,重要的字段如下
/**
* 節點的等待狀態,一個節點可能位於以下幾種狀態:
* CANCELLED = 1: 節點操作因爲超時或者對應的線程被interrupt。節點不應該不留在此狀態,一旦達到此狀態將從CHL隊列中踢出。
* SIGNAL = -1: 節點的繼任節點是(或者將要成爲)BLOCKED狀態(例如通過LockSupport.park()操作),因此一個節點一旦被釋放(解鎖)或者取消就需要喚醒(LockSupport.unpack())它的繼任節點。
* CONDITION = -2:表明節點對應的線程因爲不滿足一個條件(Condition)而被阻塞。
* 0: 正常狀態,新生的非CONDITION節點都是此狀態。
* 非負值標識節點不需要被通知(喚醒)。
*/
volatile int waitStatus;
/**
* 此節點的前一個節點。節點的waitStatus依賴於前一個節點的狀態。
*/
volatile Node prev;
/**
* 此節點的後一個節點。後一個節點是否被喚醒(uppark())依賴於當前節點是否被釋放。
*/
volatile Node next;
/**
* 節點綁定的線程。
*/
volatile Thread thread;
/**
* 下一個等待條件(Condition)的節點,由於Condition是獨佔模式,
* 因此這裏有一個簡單的隊列來描述Condition上的線程節點。
*/
Node nextWaiter;
而另一個重要的屬性則在AbstractQueuedSynchronizer 的父類AbstractOwnableSynchronizer中。
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
瞭解這些基礎之後,來看NonFairSync 的 lock函數
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//如果鎖沒有被任何線程鎖定,則用cas方式進行搶佔
if (compareAndSetState(0, 1))
//如果獲取鎖成功則設定當前線程爲鎖的擁有者
setExclusiveOwnerThread(Thread.currentThread());
else
//如果鎖已經被佔用,則嘗試加鎖,
acquire(1);
}
這裏說下acquir方法,這個方法由AbstractQueuedSynchronizer 提供
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1.如果tryAcquire(arg)成功,那就沒有問題,已經拿到鎖,整個lock()過程就結束了。如果失敗進行操作2。
2. addWaiter創建一個獨佔節點(Node)並且此節點加入CHL隊列末尾(稍後分析)。進行操作3。
3. acquireQueued 自旋嘗試獲取鎖,失敗根據前一個節點來決定是否掛起(park()),直到成功獲取到鎖。進行操作4。
4.selfInterrupt 如果當前線程已經中斷過,那麼就中斷當前線程(清除中斷位)。
這裏有點複雜,接下來會一步一步分析。
tryAcquire 被NonFairSync override ,直接調用 Sync.nonfairTryAcquire,代碼如下
/**
* Performs non-fair tryLock. tryAcquire is
* implemented in subclasses, but both need nonfair
* try for trylock method.
*/
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;
}
有點似曾相識的感覺,跟NonFairSync
的 lock函數有點類似。如果該方法失敗返回false,也就是tryAcquire失敗,進行acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 。先來看addWaiter函數
/**
* Creates and enqueues node for given thread and mode.
*
* @param current the thread
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
}
將當前線程封裝成一個node然後放入AbstractQueuedSynchronizer的node隊列。這裏考慮了隊列爲空和多線程併發的情況,所以處理的比較糾結,不過代碼倒是不復雜,耐心看可以理解。上面是節點如隊列的一部分。當前僅當隊列不爲空並且將新節點插入尾部成功後直接返回新節點。否則進入enq(Node)進行操作。
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果爲空就創建頭結點
if (t == null) { // Must initialize
Node h = new Node(); // Dummy header
h.next = node;
node.prev = h;
if (compareAndSetHead(h)) {
tail = node;
return h;
}
}
//如果這個時候因爲併發,隊列已經非空,那就把當前的node放入隊尾
else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果當前node是head的直接後繼則嘗試獲取鎖
// 這裏不會和等待隊列中其它線程發生競爭,但會和嘗試獲取鎖且尚未進入等待隊列的線程發生競爭。這是非公平鎖和公平鎖的一個重要區別。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
// 如果不是head的後繼或獲取鎖失敗,則檢查是否要禁用當前線程
// 是則禁用,直到被lock.release喚醒或線程中斷
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
}
shouldParkAfterFailedAcquire做了一件很重要的事:根據狀態對等待隊列進行清理,並設置等待信號。這裏需要先說明一下waitStatus,它是AbstractQueuedSynchronizer的靜態內部類Node的成員變量,用於記錄Node對應的線程等待狀態.等待狀態在剛進入隊列時都是0,如果等待被取消則被設爲Node.CANCELLED,若線程釋放鎖時需要喚醒等待隊列裏的其它線程則被置爲Node.SIGNAL,還有一種狀態Node.CONDITION這裏先不討論。
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
*如果前一個節點的等待狀態waitStatus 被設置爲SIGNAL,也就是前面的節點還沒有獲得到鎖,
*那麼返回true,表示當前節點(線程)就應該park()了
*/
return true;
if (ws > 0) {
/*
* 如果前一個節點的等待狀態waitStatus>0,也就是前一個節點被CANCELLED了,
*那麼就將前一個節點去掉,遞歸此操作直到所有前一個節點的waitStatus<=0。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //等於0的時候
//前一個節點等待狀態waitStatus=0,修改前一個節點狀態位爲SINGAL,
//表示後面有節點等待處理,需要根據它的等待狀態來決定是否該park()
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
/*
*這裏一定要返回false,有可能前置結點這時已經釋放了鎖,
*但因其 waitStatus在釋放鎖時還未被置爲SIGNAL而未觸發喚醒等待線程操作,
*因此必須通過return false來重新嘗試一次獲取鎖
*/
return false;
}
如果shouldParkAfterFailedAcquire 返回true那麼會調用parkAndCheckInterrupt
。實現如下,很簡單,直接禁用線程,並等待被喚醒或中斷髮生。對java中Thread.interrupted()都作了什麼不甚瞭解的要做功課。 這裏線程即被堵塞,醒來時會重試獲取鎖,失敗則繼續堵塞。即使Thread.interrupted()也無法中斷。那些想在等待時間過長時中斷退出的線程可以調用ReentrantLoc.lockInterruptibly()。
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}