Java併發之AQS原理剖析

Yanci丶 發表於 2021-06-02
Java

概述:

AbstractQueuedSynchronizer,可以稱為抽象佇列同步器。

AQS有獨佔模式和共享模式兩種:

  • 獨佔模式:

公平鎖:

非公平鎖:

  • 共享模式:

 

資料結構:

  • 基本屬性:
/**
 * 同步等待佇列的頭結點
 */
private transient volatile Node head;

/**
 * 同步等待佇列的尾結點
 */
private transient volatile Node tail;

/**
 * 同步資源狀態
 */
private volatile int state;
  • 內部類:
static final class Node {
    /**
     * 標記節點為共享模式
     */
    static final Node SHARED = new Node();
    /**
     * 標記節點為獨佔模式
     */
    static final Node EXCLUSIVE = null;

    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    /**
     *   CANCELLED:  值為1,表示當前的執行緒被取消
     *   SIGNAL: 值為-1,表示當前節點的後繼節點包含的執行緒需要執行,也就是unpark;
     *   CONDITION:  值為-2,表示當前節點在等待condition,也就是在condition佇列中;
     *   PROPAGATE:  值為-3,表示當前場景下後續的acquireShared能夠得以執行;
     *   0:  表示當前節點在sync佇列中,等待著獲取鎖。
     *  表示當前節點的狀態值
     */
    volatile int waitStatus;

    /**
     * 前置節點
     */
    volatile Node prev;

    /**
     * 後繼節點
     */
    volatile Node next;

    /**
     * 節點同步狀態的執行緒
     */
    volatile Thread thread;

    /**
     * 儲存condition佇列中的後繼節點
     */
    Node nextWaiter;

    /**
     * 是否為共享模式
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 獲取前驅結點
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

主要方法解析:

Java併發之AQS原理剖析

  • tryAcquire/tryAcquireShared(int arg)

  獨佔/共享模式獲取鎖;由子類實現,僅僅獲取鎖,獲取鎖失敗時不進行阻塞排隊。

  • tryRelease/tryReleaseShared(int arg)

  獨佔/共享模式釋放鎖;由子類實現,僅僅釋放鎖,釋放鎖成功不對後繼節點進行喚醒操作。

  • acquire/acquireShared(int arg)

  獨佔/共享模式獲取鎖,如果執行緒被中斷喚醒,會返回執行緒中斷狀態,不會拋異常中止執行操作(忽略中斷)。

  • acquireInterruptibly/acquireSharedInterruptibly(int arg)

  獨佔/共享模式獲取鎖,執行緒如果被中斷喚醒,則丟擲InterruptedException異常(中斷即中止)。

  • tryAcquireNanos/tryAcquireSharedNanos(int arg, long nanosTimeout)

  獨佔/共享時間中斷模式獲取鎖,執行緒如果被中斷喚醒,則丟擲InterruptedException異常(中斷即中止);如果超出等待時間則返回加鎖失敗。

  • release/releaseShared(int arg)

  獨佔/共享模式釋放鎖。

  • addWaiter(Node mode)

  將給定模式節點進行入隊操作。

 1     private Node addWaiter(Node mode) {
 2         // 根據指定模式,新建一個當前節點的物件
 3         Node node = new Node(Thread.currentThread(), mode);
 4         // Try the fast path of enq; backup to full enq on failure
 5         Node pred = tail;
 6         if (pred != null) {
 7             // 將當前節點的前置節點指向之前的尾結點
 8             node.prev = pred;
 9             // 將當前等待的節點設定為尾結點(原子操作)
10             if (compareAndSetTail(pred, node)) {
11                 // 之前尾結點的後繼節點設定為當前等待的節點
12                 pred.next = node;
13                 return node;
14             }
15         }
16         enq(node);
17         return node;
18     }
  • enq(final Node node)

  將節點設定為尾結點。注意這裡會進行自旋操作,確保節點設定成功。因為等待的執行緒需要被喚醒操作;如果操作失敗,當前節點沒有與其他節點沒有引用指向關係,一直就不會被喚醒(除非程式程式碼中斷執行緒)。

 1     private Node enq(final Node node) {
 2         for (;;) {
 3             Node t = tail;
 4             // 判斷尾結點是否為空,尾結點初始值是為空
 5             if (t == null) { // Must initialize
 6                 // 尾結點為空,需要初始化
 7                 if (compareAndSetHead(new Node()))
 8                     tail = head;
 9             } else {
10                 // 設定當前節點設定為尾結點
11                 node.prev = t;
12                 if (compareAndSetTail(t, node)) {
13                     t.next = node;
14                     return t;
15                 }
16             }
17         }
18     }
  • acquireQueued(final Node node, int arg)

  已經在佇列當中的節點,準備阻塞獲取鎖。在阻塞前會判斷前置節點是否為頭結點,如果為頭結點;這時會嘗試獲取下鎖(因為這時頭結點有可能會釋放鎖)。

 1     final boolean acquireQueued(final Node node, int arg) {
 2         boolean failed = true;
 3         try {
 4             boolean interrupted = false;
 5             for (;;) {
 6                 // 當前節點的前置節點
 7                 final Node p = node.predecessor();
 8                 // 入隊前會先判斷下該節點的前置節點是否是頭節點(此時頭結點有可能會釋放鎖);然後嘗試去搶鎖
 9                 // 在非公平鎖場景下有可能會搶鎖失敗,這時候會繼續往下執行 阻塞執行緒
10                 if (p == head && tryAcquire(arg)) {
11                     //如果搶到鎖,將頭節點後移(也就是將該節點設定為頭結點)
12                     setHead(node);
13                     p.next = null; // help GC
14                     failed = false;
15                     return interrupted;
16                 }
17                 // 如果前置節點不是頭結點,或者當前節點搶鎖失敗;通過shouldParkAfterFailedAcquire判斷是否應該阻塞
18                 // 當前置節點的狀態為SIGNAL=-1,才可以安全被parkAndCheckInterrupt阻塞執行緒
19                 if (shouldParkAfterFailedAcquire(p, node) &&
20                     parkAndCheckInterrupt())
21                     // 該執行緒已被中斷
22                     interrupted = true;
23             }
24         } finally {
25             if (failed)
26                 cancelAcquire(node);
27         }
28     }
  • shouldParkAfterFailedAcquire(Node pred, Node node)

  檢查和更新未能獲取鎖節點的狀態,返回是否可以被安全阻塞。

 1     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 2         int ws = pred.waitStatus;   // 獲取前置節點的狀態
 3         if (ws == Node.SIGNAL)
 4             /*
 5              * 前置節點的狀態waitStatus為SIGNAL=-1,當前執行緒可以安全的阻塞
 6              */
 7             return true;
 8         if (ws > 0) {
 9             /*
10              * 如果前置節點的狀態waitStatus>0,即waitStatus為CANCELLED=1(無效節點),需要從同步狀態佇列中取消等待(移除佇列)
11              */
12             do {
13                 node.prev = pred = pred.prev;
14             } while (pred.waitStatus > 0);
15             pred.next = node;
16         } else {
17             /*
18              * 將前置狀態的waitStatus修改為SIGNAL=-1,然後當前節點才可以被安全的阻塞
19              */
20             compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
21         }
22         return false;
23     }
  • parkAndCheckInterrupt()

  阻塞當前節點,返回當前執行緒的中斷狀態。

1     private final boolean parkAndCheckInterrupt() {
2         LockSupport.park(this); //阻塞
3         return Thread.interrupted();
4     }
  • cancelAcquire(Node node)

  取消進行的獲取鎖操作,在非忽略中斷模式下,執行緒被中斷喚醒拋異常時會呼叫該方法。

 1     //  將當前節點的狀態設定為CANCELLED,無效的節點,同時移除佇列       
 2     private void cancelAcquire(Node node) {
 3         if (node == null)
 4             return;
 5 
 6         node.thread = null;
 7         Node pred = node.prev;
 8         while (pred.waitStatus > 0)
 9             node.prev = pred = pred.prev;
10 
11         Node predNext = pred.next;
12         node.waitStatus = Node.CANCELLED;
13         if (node == tail && compareAndSetTail(node, pred)) {
14             compareAndSetNext(pred, predNext, null);
15         } else {
16             int ws;
17             if (pred != head &&
18                 ((ws = pred.waitStatus) == Node.SIGNAL ||
19                  (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
20                 pred.thread != null) {
21                 Node next = node.next;
22                 if (next != null && next.waitStatus <= 0)
23                     compareAndSetNext(pred, predNext, next);
24             } else {
25                 unparkSuccessor(node);
26             }
27 
28             node.next = node; // help GC
29         }
30     }    
  • hasQueuedPredecessors()

  判斷當前執行緒是否應該排隊。

  1.第一種結果——返回true:(1.1和1.2同時存在,1.2.1和1.2.2有一個存在)

    1.1  h != t為true,說明頭結點和尾結點不相等,表示佇列中至少有兩個不同節點存在,至少有一點不為null。

    1.2  ((s = h.next) == null || s.thread != Thread.currentThread())為true

      1.2.1   (s = h.next) == null為true,表示頭結點之後沒有後續節點。

      1.2.2   (s = h.next) == null為false,s.thread != Thread.currentThread()為true
          表示頭結點之後有後續節點,但是頭節點的下一個節點不是當前執行緒

  2.第二種結果——返回false,無需排隊。(2.1和2.2有一個存在)

    2.1  h != t為false,即h == t;表示h和t同時為null或者h和t是同一個節點,無後續節點。

    2.2  h != t為true,((s = h.next) == null || s.thread != Thread.currentThread())為false

      表示佇列中至少有兩個不同節點存在,同時持有鎖的執行緒為當前執行緒。

1     public final boolean hasQueuedPredecessors() {
2         Node t = tail; // Read fields in reverse initialization order
3         Node h = head;
4         Node s;
5         return h != t &&
6             ((s = h.next) == null || s.thread != Thread.currentThread());
7     }