1. 程式人生 > >AQS 獨佔式鎖如何實現執行緒同步

AQS 獨佔式鎖如何實現執行緒同步

獨佔式鎖為了實現執行緒同步,主要是綜合使用了三種手段。

  1. 雙向連結串列構成的佇列
  2. CAS原子操作
  3. 阻塞與釋放(ParkSupport的park( )和unpark( )方法)

執行緒獲得鎖的流程如下:

  1. 先請求獲得鎖, 如果成功了就沒什麼說得,如果不成功則請求佇列
  2. 請求佇列的過程如圖:
    在這裡插入圖片描述

重要程式碼跟蹤:

// 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);
    }