AbstractQueuedSynchronizer源碼閱讀

AbstractQueuedSynchronizer 就是那個大名鼎鼎的 AQS,是java.util.concurrent包下同步器的核心。java

CLH(Craig, Landin, and Hagersten)鎖

使用隊列的方式來解決n個線程來爭奪m把鎖的問題,每當一個新的線程須要獲取鎖,爲其建立一個節點並放到隊尾,若是該線程是隊列中的第一個節點,則節點的locked設置成false,若是它不是隊列的第一個節點,則它的節點的prev指向原來的隊尾節點,並不斷自旋查看prev指向節點的locked屬性,若是該值變爲false,表示輪到它來嘗試獲取鎖了,若是獲取成功並最終用完釋放後,則將本身的locked設置成false,若是獲取失敗,locked值不變,仍是true,並不斷嘗試獲取鎖。node

也就是說,每一個節點只須要關心前置節點的locked狀態,能夠發現CLH實現鎖的獲取是公平的。ui

Node節點

Node節點維護了雙向節點和當前節點狀態和線程引用。this

static final class Node {
volatile Node prev;
volatile Node next;
volatile Thread thread;

volatile int waitStatus; // SIGNAL CANCELLED CONDITION PROPAGATE

CANCELLED,值爲1,表示當前的線程被取消;
SIGNAL,值爲-1,表示當前節點的後繼節點包含的線程須要運行,也就是unpark;
CONDITION,值爲-2,表示當前節點在等待condition,也就是在condition隊列中;
PROPAGATE,值爲-3,表示當前場景下後續的acquireShared可以得以執行;
值爲0,表示當前節點在sync隊列中,等待着獲取鎖。

}

FIFO隊列

*      +------+  prev +-----+       +-----+
 * head |      | <---- |     | <---- |     |  tail
 *      +------+       +-----+       +-----+

核心屬性

  • private transient volatile Node head; 等待隊列首元素;
  • private transient volatile Node tail; 等待隊列尾元素;
  • private volatile int state; 同步狀態;

須要在鎖定時,須要維護一個狀態(int類型),而對狀態的操做是原子和非阻塞的,經過同步器提供的對狀態訪問的方法對狀態進行操縱,並基於Unsafe的原子操做來修改state的狀態,compareAndSet來確保原子性的修改。線程

獲取/設置當前的同步狀態:code

protected final int getState() {
        return state;
    }

protected final void setState(int newState) {
        state = newState;
    }

對同步狀態對修改採用原子操做:隊列

protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

入隊操做,若是隊列爲空則實例化節點,不然插入隊尾:get

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;
    }
  • protected boolean tryAcquire(int arg) 排它的獲取這個狀態。這個方法的實現須要查詢當前狀態是否容許獲取,而後再進行獲取(使用compareAndSetState來作)狀態。
  • protected boolean tryRelease(int arg) 釋放狀態。
  • protected int tryAcquireShared(int arg) 共享的模式下獲取狀態。
  • protected boolean tryReleaseShared(int arg) 共享的模式下釋放狀態。
  • protected boolean isHeldExclusively() 在排它模式下,狀態是否被佔用。

獲取鎖: 須要獲取當前節點的前驅節點,而且頭結點可以獲取狀態,表明可以佔有鎖。同步

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

這個方法在非公平實現中,主要是經過AQS的state來檢查和維護鎖狀態,若是state是0,說明沒有線程佔有這個鎖,若是不爲0而且鎖的佔有線程是當前線程,則是重入的狀況,都可以得到鎖並修改state值。it

若是是首次得到鎖,則設置鎖佔有線程爲當前線程。

固然,若是前面兩種狀況都不知足,說明嘗試得到鎖失敗,須要作前面段落所述的隊列操做,建立一個等待結點並進入循環,循環中的park()調用掛起當前線程。

不然將當前線程掛起:

LockSupport.park最終把線程交給系統(Linux)內核進行阻塞。

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

若是修改state值成功,則找到隊列中應該喚起的結點,對節點中的線程調用unpark()方法,恢復線程執行。

相關文章
相關標籤/搜索