exclusive mode加鎖:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1 首先嚐試加鎖,其中tryAcquire需要子類實現。裏面的主要的邏輯是使用CAS把狀態修改爲arg,如果CAS修改成功,那麼標誌加鎖成功。如果失敗,調用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;
}
2 上面是addWaiter的邏輯,首先把當前線程包裝成一個node,然後嘗試把node設置爲CLH等待隊列的尾部。如果失敗,那麼調用enq,循環CAS,直到成功。可以看到入隊是通過CAS實現的。
3 把當前線程入隊後,調用acquireQueued方法,下面是代碼:
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
}
這是A.Q.S的核心邏輯,首先看shouldParhkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
如果前繼節點的狀態是signal,那麼前繼節點在release的時候,會喚醒當前節點,那麼可以安全的阻塞了。如果前繼節點是cancelled,那麼需要把這些節點全部刪除。如果前繼節點是0或者PROPAGATE,需要把狀態修改爲signal。返回false的原因是需要再次調用tryAcquire確認是否能拿到鎖。
如果shouldParhkAfterFailedAcquire返回true,那麼parkAndCheckInterrupt會被調用,裏面的邏輯很簡單,調用LockSupport阻塞當前線程:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
LockSuppot.park只有在其他線程調用unpark的時候纔會返回。因爲acquire是忽略其他線程的interrupt的,有上層代碼處理。
再返回到acquireQueued方法,檢查前繼節點是否是頭結點(頭結點就是當前拿到鎖的線程的節點),如果是再次嘗試加鎖,如果成功,那麼把頭結點設置爲當前節點。如果失敗,那麼再次阻塞自己。
可以看到acquire的主要邏輯是首先嚐試加鎖,如果加鎖失敗,那麼把線程放到CLH隊列中然後阻塞。其他線程release喚醒後,繼續嘗試加鎖,如果失敗,再次阻塞的這樣一個循環。