AQS 獨佔式鎖如何實現執行緒同步
阿新 • • 發佈:2018-11-06
獨佔式鎖為了實現執行緒同步,主要是綜合使用了三種手段。
- 雙向連結串列構成的佇列
- CAS原子操作
- 阻塞與釋放(ParkSupport的park( )和unpark( )方法)
執行緒獲得鎖的流程如下:
- 先請求獲得鎖, 如果成功了就沒什麼說得,如果不成功則請求佇列
- 請求佇列的過程如圖:
重要程式碼跟蹤:
// lock方法呼叫 acquire 方法請求獲得鎖 final void lock() { acquire(1); } // 這是獲得鎖的方法,引數arg的代表鎖的型別:獨佔鎖為1 public final void acquire(int arg) { // 可以看出,如果不能獲得鎖,則請求佇列。再請求佇列時先生成節點 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } ----------------------------------------------------------------------------------------------- @節點的新增 // 這是生成節點的具體實現 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); Node pred = tail; // 如果尾部節點為null,說明當前佇列還沒有節點 if (pred != null) { // 設定新節點的前置節點為佇列的尾部節點 node.prev = pred; // 並做新增操作,如果成功則返回當前節點 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 這個方法是使用CAS的自旋確保節點成功新增,原始碼見下一個方法 enq(node); return node; } // 通過CAS加自旋新增節點 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; } } } } ----------------------------------------------------------------------------------------------- // 請求佇列 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); } } // 阻塞的方法 private final boolean parkAndCheckInterrupt() { // 阻塞 LockSupport.park(this); return Thread.interrupted(); } ----------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------- 阻塞的方法什麼時候被喚醒? // 釋放鎖 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) // 嘗試釋放鎖成功後,喚醒下一個被阻塞節點,程式碼見下一個方法 unparkSuccessor(h); return true; } return false; } // 喚醒當前節點(引數node)的下一個節點 private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 得到下一個節點 Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) // 如果下一個節點不為null,就喚醒。被喚醒的節點處在for迴圈中,繼續做自己的事。新一輪請求獲得鎖邏輯開始。 LockSupport.unpark(s.thread); }