一文帶你學會AQS和并發(fā)工具類的關系

1. 存在的意義

??AQS(AbstractQueuedSynchronizer)是JAVA中眾多鎖以及并發(fā)工具的基礎,其底層采用樂觀鎖露筒,大量使用了CAS操作, 并且在沖突時敌卓,采用自旋方式重試慎式,以實現(xiàn)輕量級和高效地獲取鎖。

??提供一個框架趟径,用于實現(xiàn)依賴于先進先出(FIFO)等待隊列的阻塞鎖和相關的同步器(semaphore等)瘪吏。 此類旨在為大多數(shù)依賴單個原子int值表示狀態(tài)的同步器提供有用的基礎。 子類必須定義更改此狀態(tài)的受保護方法蜗巧,并定義該狀態(tài)對于獲取或釋放此對象而言意味著什么掌眠。 鑒于這些,此類中的其他方法將執(zhí)行所有排隊和阻塞機制幕屹。 子類可以維護其他狀態(tài)字段蓝丙,但是相對于同步,僅跟蹤使用方法getState,setState和compareAndSetState操作的原子更新的int值望拖。

??此類支持默認獨占模式和共享模式之一或兩者渺尘。 當以獨占方式進行獲取時,其他線程嘗試進行的獲取將無法成功说敏。 由多個線程獲取的共享模式可能(但不一定)成功沧烈。 該類不“理解”這些差異,當共享模式獲取成功時像云,下一個等待線程(如果存在)還必須確定它是否也可以獲取锌雀。 在不同模式下等待的線程共享相同的FIFO隊列。 通常迅诬,實現(xiàn)子類僅支持這些模式之一腋逆,但例如可以在ReadWriteLock發(fā)揮作用。 僅支持獨占模式或僅支持共享模式的子類無需定義支持未使用模式的方法侈贷。


2. 核心知識點

2.1 state

private volatile int state; // 同步狀態(tài)

??state是整個工具的核心惩歉,通常整個工具都是在設置和修改狀態(tài),很多方法的操作都依賴于當前狀態(tài)是什么俏蛮。由于狀態(tài)是全局共享的撑蚌,一般會被設置成volatile類型,為了保證其修改的可見性搏屑;

2.2 CLH隊列

??AQS中的隊列是CLH變體的虛擬雙向隊列(FIFO)争涌,AQS是通過將每條請求共享資源的線程封裝成一個節(jié)點來實現(xiàn)鎖的分配。隊列采用的是悲觀鎖的思想辣恋,表示當前所等待的資源亮垫,狀態(tài)或者條件短時間內可能無法滿足模软。因此,它會將當前線程包裝成某種類型的數(shù)據(jù)結構饮潦,扔到一個等待隊列中燃异,當一定條件滿足后,再從等待隊列中取出继蜡。

2.3 CAS操作

??CAS操作是最輕量的并發(fā)處理回俐,通常我們對于狀態(tài)的修改都會用到CAS操作,因為狀態(tài)可能被多個線程同時修改稀并,CAS操作保證了同一個時刻仅颇,只有一個線程能修改成功,從而保證了線程安全稻轨。CAS采用的是樂觀鎖的思想灵莲,因此常常伴隨著自旋雕凹,如果發(fā)現(xiàn)當前無法成功地執(zhí)行CAS殴俱,則不斷重試,直到成功為止枚抵。


3. 核心實現(xiàn)原理

3.1 作為同步器的基礎

??要將此類用作同步器的基礎线欲,請使用getState,setState或compareAndSetState檢查或修改同步狀態(tài)汽摹,以重新定義以下方法(如適用):

  1. tryAcquire

獨占方式李丰,arg為獲取鎖的次數(shù),嘗試獲取資源逼泣,成功則返回True趴泌,失敗則返回False。

  1. tryRelease

獨占方式拉庶,arg為釋放鎖的次數(shù)嗜憔,嘗試釋放資源,成功則返回True氏仗,失敗則返回False吉捶。

  1. tryAcquireShared

共享方式,arg為獲取鎖的次數(shù)皆尔,嘗試獲取資源呐舔。負數(shù)表示失敗慷蠕;0表示成功珊拼,但沒有剩余可用資源;正數(shù)表示成功流炕,且有剩余資源杆麸。

  1. tryReleaseShared

共享方式搁进,arg為釋放鎖的次數(shù)抵怎,嘗試釋放資源废境,如果釋放后允許喚醒后續(xù)等待結點返回True长搀,否則返回False逻淌。

  1. isHeldExclusively

該線程是否正在獨占資源曾撤。只有用到Condition才需要去實現(xiàn)它刚夺。

??默認情況下果录,這些方法中的每一個都會引發(fā)UnsupportedOperationException 咽袜。 這些方法的實現(xiàn)必須在內部是線程安全的讹开,并且通常應簡短且不阻塞盅视。 定義這些方法是使用此類的唯一受支持的方法。 所有其他方法都被聲明為final方法旦万,因為它們不能獨立變化闹击。

3.2 同步狀態(tài)state

??AQS中維護了一個名為state的字段,意為同步狀態(tài)成艘,是由volatile修飾的赏半,用于展示當前臨界資源的獲鎖情況。

private volatile int state;

下面提供了幾個訪問這個state字段的方法:
返回同步狀態(tài)的當前值淆两。 此操作具有volatile讀取的內存語義

protected final int getState() {
    return state;
}

設置同步狀態(tài)的值断箫。 此操作具有volatile寫操作的內存語義。

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

??如果當前狀態(tài)值等于期望值秋冰,則以原子方式將同步狀態(tài)設置為給定的更新值仲义。 此操作具有volatile讀寫的內存語義

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

??這幾個方法都是Final修飾的,說明子類中無法重寫它們剑勾。我們可以通過修改State字段表示的同步狀態(tài)來實現(xiàn)多線程的獨占模式和共享模式
state的值即表示了鎖的狀態(tài)埃撵,state為0表示鎖沒有被占用,state大于0表示當前已經有線程持有該鎖虽另,這里之所以說大于0而不說等于1是因為可能存在可重入的情況暂刘。你可以把state變量當做是當前持有該鎖的線程數(shù)量。

public abstract class AbstractOwnableSynchronizer
    protected AbstractOwnableSynchronizer() {
      
    private transient Thread exclusiveOwnerThread;
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

exclusiveOwnerThread 屬性的值即為當前持有鎖的線程獨占模式獲取鎖流程:

圖片

共享模式獲取鎖流程:

圖片

3.3 數(shù)據(jù)結構

  1. AQS中最基本的數(shù)據(jù)結構是Node洲赵,Node即為CLH變體隊列中的節(jié)點鸳惯。
static final class Node {
    // 表示線程以共享的模式等待鎖
    static final Node SHARED = new Node();
    // 表示線程正在以獨占的方式等待鎖
    static final Node EXCLUSIVE = null;
    // 為1,表示線程獲取鎖的請求已經取消了
    static final int CANCELLED =  1;
    // 為-1叠萍,表示線程已經準備好了芝发,就等資源釋放了
    static final int SIGNAL    = -1;
    // 為-2,表示節(jié)點在等待隊列中苛谷,節(jié)點線程等待喚醒
    static final int CONDITION = -2;
    // 為-3辅鲸,當前線程處在SHARED情況下,該字段才會使用
    static final int PROPAGATE = -3;
    // 當前節(jié)點在隊列中的狀態(tài)
    volatile int waitStatus;
    // 前驅節(jié)點
    volatile Node prev;
    // 后續(xù)節(jié)點
    volatile Node next;
    // 當前節(jié)點的線程
    volatile Thread thread;
    // 指向下一個處于CONDITION狀態(tài)的節(jié)點
    Node nextWaiter;
    ...
}
  1. AQS中CLH變體的虛擬雙向隊列(FIFO)腹殿,AQS是通過將每條請求共享資源的線程封裝成一個節(jié)點來實現(xiàn)鎖的分配独悴。
// 隊列頭節(jié)點
private transient volatile Node head;
// 隊列尾節(jié)點
private transient volatile Node tail;

圖片

??在AQS中的隊列是一個FIFO隊列例书,它的head節(jié)點永遠是一個虛擬結點(dummy node), 它不代表任何線程,因此head所指向的Node的thread屬性永遠是null刻炒。但是我們不會在構建過程中創(chuàng)建它們决采,因為如果沒有爭用,這將是浪費時間坟奥。 而是構造節(jié)點树瞭,并在第一次爭用時設置頭和尾指針。只有從次頭節(jié)點往后的所有節(jié)點才代表了所有等待鎖的線程爱谁。也就是說晒喷,在當前線程沒有搶到鎖被包裝成Node扔到隊列中時,即使隊列是空的访敌,它也會排在第二個凉敲,我們會在它的前面新建一個虛擬節(jié)點。

4. 獲取鎖實現(xiàn)

4.1 ReentrantLock 獨占鎖內部結構

構造函數(shù)源代碼

// 默認創(chuàng)建非公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
// 通過傳值為true來進行創(chuàng)建公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock 里面有三個內部類:

  1. 一個是抽象的 Sync 實現(xiàn)了 AbstractQueuedSynchronizer
  2. NonfairSync 繼承了 Sync
  3. FairSync 繼承了 Sync
圖片

4.2 非公平鎖的實現(xiàn)

ReentrantLock 種獲取鎖的方法

public void lock() {
    sync.lock();
}

ReentrantLock 的非公平鎖實現(xiàn)

static final class NonfairSync extends Sync {
    final void lock() {
    // 如果設置state的值從0變?yōu)?成功
        if (compareAndSetState(0, 1))
        // 則將當前線程設置為獨占線程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

compareAndSetState(0,1)

protected final boolean compareAndSetState(int expect, int update) {
    // 通過unsafe.compareAndSwapInt方法來進行設置值
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

stateOffset 為AQS種維護的state屬性的偏移量

圖片

setExclusiveOwnerThread(Thread.currentThread());

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

acquire(1); 調用的是AQS 中的acquire(int arg) 方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire(arg) 該方法是protected的由子類去具體實現(xiàn)的

圖片

圖片

??我們需要看的是NonfairSync中實現(xiàn)的tryAcquire方法,里面又調用了nonfairTryAcquire方法寺旺,再進去看看

static final class NonfairSync extends Sync {
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

nonfairTryAcquire(int acquires) 方法實現(xiàn)

final boolean nonfairTryAcquire(int acquires) {
    // 獲取當前線程
    final Thread current = Thread.currentThread();
    // 獲取當前state的值
    int c = getState(); 
    if (c == 0) {
      // 看看設置值是否能成功
            if (compareAndSetState(0, acquires)) {
           // 則將當前線程設置為獨占線程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
 // 返回由setExclusiveOwnerThread設置的最后一個線程爷抓;如果從不設置,則返回null 
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 設置state的值
        setState(nextc);
        return true;
    }
    return false;
}

acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法實現(xiàn)迅涮,先看里addWaiter(Node.EXCLUSIVE)方法注意:Node.EXCLUSIVE 此時是空值废赞,所以mode 就是空的徽龟,所以此時創(chuàng)建的Node節(jié)點中的nextWaiter是空值叮姑。

static final class Node {
  Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
  }
}
private Node addWaiter(Node mode) {
    // 創(chuàng)建一個新的節(jié)點
    Node node = new Node(Thread.currentThread(), mode);
    // 將當前CLH隊列的尾部節(jié)點賦予給 pred
    Node pred = tail;
    if (pred != null) { // 如果尾節(jié)點不為空
        node.prev = pred; // 將當前node節(jié)點的前驅節(jié)點指向CLH隊列的尾部節(jié)點
        if (compareAndSetTail(pred, node)) { // CAS設置值
            pred.next = node; // CLH隊列的尾部節(jié)點的后繼節(jié)點指向新的node節(jié)點
            return node;
        }
    }
    enq(node);
    return node;
}

如果CLH隊列的尾部節(jié)點為空值的話,執(zhí)行enq(node)方法

// 通過CAS方式設置隊列的頭節(jié)點
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
// 通過CAS方式設置隊列的尾部節(jié)點
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
// 節(jié)點入隊操作
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 如果尾部節(jié)點為空值
        if (t == null) {
        // 則進行初始化一個節(jié)點据悔,將其設置為頭節(jié)點
            if (compareAndSetHead(new Node()))
                tail = head; //頭尾指向同一個空節(jié)點
        } else {
      // 如果尾部節(jié)點不為空传透,那么將傳進來的入隊節(jié)點的前驅節(jié)點指向當前隊列的尾部節(jié)點
            node.prev = t;
      // 將當前傳進來的入隊節(jié)點設置為當前隊列的尾部節(jié)點
            if (compareAndSetTail(t, node)) { 
          // 將當前隊列的尾部節(jié)點的后繼節(jié)點指向傳進來的新節(jié)點
                 t.next = node;
                return t;
            }
        }
    }
}

查看 acquireQueued 方法實現(xiàn)

// 獲取當前節(jié)點的前驅節(jié)點
final Node predecessor() throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}
// 檢查并更新無法獲取的節(jié)點的狀態(tài)。 如果線程應阻塞极颓,則返回true
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//SIGNAL這個狀態(tài)就有點意思了朱盐,它不是表征當前節(jié)點的狀態(tài),而是當前節(jié)點的下一個節(jié)點 //的狀態(tài)菠隆。當一個節(jié)點的waitStatus被置為SIGNAL兵琳,就說明它的下一個節(jié)點(即它的后繼 // 節(jié)點)已經被掛起了(或者馬上就要被掛起了),因此在當前節(jié)點釋放了鎖或者放棄獲取 // 鎖時骇径,如果它的waitStatus屬性為SIGNAL躯肌,它還要完成一個額外的操作——喚醒它的后繼節(jié)點。
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
     // 當前節(jié)點的 ws > 0, 則為 Node.CANCELLED 說明前驅節(jié)點                                        // 已經取消了等待鎖(由于超時或者中斷等原因)
        // 既然前驅節(jié)點不等了, 那就繼續(xù)往前找, 直到找到一個還在等待鎖的節(jié)點
        // 然后我們跨過這些不等待鎖的節(jié)點, 直接排在等待鎖的節(jié)點的后面 
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
     // 前驅節(jié)點的狀態(tài)既不是SIGNAL破衔,也不是CANCELLED
        // 用CAS設置前驅節(jié)點的ws為 Node.SIGNAL清女,給自己定一個鬧鐘
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
// 停止的便捷方法,然后檢查是否中斷
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
// 取消正在進行的獲取嘗試
private void cancelAcquire(Node node) {
    if (node == null)
        return;
    node.thread = null;
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    Node predNext = pred.next;
    node.waitStatus = Node.CANCELLED;
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }
        node.next = node;
    }
}
// 能執(zhí)行到該方法, 說明addWaiter 方法已經成功將包裝了當前Thread的節(jié)點添加到了等待隊列的隊尾
// 該方法中將再次嘗試去獲取鎖
// 在再次嘗試獲取鎖失敗后, 判斷是否需要把當前線程掛起
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
         // 獲取當前節(jié)點的前驅節(jié)點
            final Node p = node.predecessor();
         // 在前驅節(jié)點就是head節(jié)點的時候,繼續(xù)嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 將當前線程掛起,使CPU不再調度它
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) 
            cancelAcquire(node);
    }
}

??為什么前面獲取鎖失敗了, 這里還要再次嘗試獲取鎖呢?
首先, 這里再次嘗試獲取鎖是基于一定的條件的,即:當前節(jié)點的前驅節(jié)點就是HEAD節(jié)點晰筛,因為我們知道嫡丙,head節(jié)點就是個虛擬節(jié)點拴袭,它不代表任何線程,或者代表了持有鎖的線程曙博,如果當前節(jié)點的前驅節(jié)點就是head節(jié)點拥刻,那就說明當前節(jié)點已經是排在整個等待隊列最前面的了。

setHead(node); 方法

// 這個方法將head指向傳進來的node,并且將node的thread和prev屬性置為null
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

??可以看出父泳,這個方法的本質是丟棄原來的head泰佳,將head指向已經獲得了鎖的node。但是接著又將該node的thread屬性置為null了尘吗,這某種意義上導致了這個新的head節(jié)點又成為了一個虛擬節(jié)點逝她,它不代表任何線程。為什么要這樣做呢睬捶,因為在tryAcquire調用成功后黔宛,exclusiveOwnerThread屬性就已經記錄了當前獲取鎖的線程了,此處沒有必要再記錄擒贸。這某種程度上就是將當前線程從等待隊列里面拿出來了臀晃,是一個變相的出隊操作。
shouldParkAfterFailedAcquire(Node pred, Node node)方法

  1. 如果為前驅節(jié)點的waitStatus值為 Node.SIGNAL 則直接返回 true
  2. 如果為前驅節(jié)點的waitStatus值為 Node.CANCELLED (ws > 0), 則跳過那些節(jié)點, 重新尋找正常等待中的前驅節(jié)點介劫,然后排在它后面徽惋,返回false
  3. 其他情況, 將前驅節(jié)點的狀態(tài)改為 Node.SIGNAL, 返回false

acquireQueued方法中的Finally代碼

private void cancelAcquire(Node node) {
  // 將無效節(jié)點過濾
    if (node == null)
        return;
  // 設置該節(jié)點不關聯(lián)任何線程,也就是虛節(jié)點
    node.thread = null;
    Node pred = node.prev;
  // 通過前驅節(jié)點座韵,跳過取消狀態(tài)的node
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
  // 獲取過濾后的前驅節(jié)點的后繼節(jié)點
    Node predNext = pred.next;
  // 把當前node的狀態(tài)設置為CANCELLED
    node.waitStatus = Node.CANCELLED;
  // 如果當前節(jié)點是尾節(jié)點险绘,將從后往前的第一個非取消狀態(tài)的節(jié)點設置為尾節(jié)點
  // 更新失敗的話,則進入else誉碴,如果更新成功宦棺,將tail的后繼節(jié)點設置為null
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
    // 如果當前節(jié)點不是head的后繼節(jié)點,
    // 1.判斷當前節(jié)點前驅節(jié)點的是否為SIGNAL
    // 2.如果不是黔帕,則把前驅節(jié)點設置為SINGAL看是否成功
    // 如果1和2中有一個為true代咸,再判斷當前節(jié)點的線程是否為null
    // 如果上述條件都滿足,把當前節(jié)點的前驅節(jié)點的后繼指針指向當前節(jié)點的后繼節(jié)點
        if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
   // 如果當前節(jié)點是head的后繼節(jié)點成黄,或者上述條件不滿足呐芥,那就喚醒當前節(jié)點的后繼節(jié)點
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

4.3 非公平鎖獲取流程圖

非公平鎖獲取鎖成功的流程圖

圖片

非公平鎖獲取鎖失敗的流程圖

圖片

5.釋放鎖實現(xiàn)

5.1釋放鎖代碼分析

??嘗試釋放此鎖。如果當前線程是此鎖的持有者奋岁,則保留計數(shù)將減少思瘟。 如果保持計數(shù)現(xiàn)在為零,則釋放鎖定厦取。 如果當前線程不是此鎖的持有者潮太,則拋出IllegalMonitorStateException。

## ReentrantLock
public void unlock() {
    sync.release(1);
}

sync.release(1) 調用的是AbstractQueuedSynchronizer中的release方法

## AbstractQueuedSynchronizer
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

分析tryRelease(arg)方法


圖片

tryRelease(arg)該方法調用的是ReentrantLock中

protected final boolean tryRelease(int releases) {
// 獲取當前鎖持有的線程數(shù)量和需要釋放的值進行相減
    int c = getState() - releases; 
    // 如果當前線程不是鎖占有的線程拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果此時c = 0就意味著state = 0,當前鎖沒有被任意線程占有
    // 將當前所的占有線程設置為空
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 設置state的值為 0
    setState(c);
    return free;
}

??如果頭節(jié)點不為空铡买,并且waitStatus != 0更鲁,喚醒后續(xù)節(jié)點如果存在的話。
這里的判斷條件為什么是h != null && h.waitStatus != 0奇钞?

??因為h == null的話澡为,Head還沒初始化。初始情況下景埃,head == null媒至,第一個節(jié)點入隊,Head會被初始化一個虛擬節(jié)點谷徙。所以說拒啰,這里如果還沒來得及入隊,就會出現(xiàn)head == null 的情況完慧。

  1. h != null && waitStatus == 0 表明后繼節(jié)點對應的線程仍在運行中谋旦,不需要喚醒
  2. h != null && waitStatus < 0 表明后繼節(jié)點可能被阻塞了,需要喚醒
private void unparkSuccessor(Node node) {
// 獲取頭結點waitStatus
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
// 獲取當前節(jié)點的下一個節(jié)點
    Node s = node.next;
//如果下個節(jié)點是null或者下個節(jié)點被cancelled屈尼,就找到隊列最開始的非cancelled的節(jié)點
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 就從尾部節(jié)點開始找往前遍歷册着,找到隊列中第一個waitStatus<0的節(jié)點。
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
  // 如果當前節(jié)點的下個節(jié)點不為空脾歧,而且狀態(tài)<=0甲捏,就把當前節(jié)點喚醒
    if (s != null)
        LockSupport.unpark(s.thread);
}

??為什么要從后往前找第一個非Cancelled的節(jié)點呢?
看一下addWaiter方法

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

??我們從這里可以看到鞭执,節(jié)點入隊并不是原子操作司顿,也就是說,node.prev = pred, compareAndSetTail(pred, node) 這兩個地方可以看作Tail入隊的原子操作蚕冬,但是此時pred.next = node;還沒執(zhí)行免猾,如果這個時候執(zhí)行了unparkSuccessor方法是辕,就沒辦法從前往后找了囤热,所以需要從后往前找。還有一點原因获三,在產生CANCELLED狀態(tài)節(jié)點的時候旁蔼,先斷開的是Next指針,Prev指針并未斷開疙教,因此也是必須要從后往前遍歷才能夠遍歷完全部的Node棺聊。
所以,如果是從前往后找贞谓,由于極端情況下入隊的非原子操作和CANCELLED節(jié)點產生過程中斷開Next指針的操作限佩,可能會導致無法遍歷所有的節(jié)點。所以,喚醒對應的線程后祟同,對應的線程就會繼續(xù)往下執(zhí)行作喘。

5.2 釋放鎖流程圖

圖片

6.注意

??由于篇幅較長公平鎖的實現(xiàn)在下一篇的博客中講述,謝謝大家的關注和支持!有問題希望大家指出晕城,共同進步!!!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末泞坦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子砖顷,更是在濱河造成了極大的恐慌贰锁,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滤蝠,死亡現(xiàn)場離奇詭異豌熄,居然都是意外死亡,警方通過查閱死者的電腦和手機物咳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門房轿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人所森,你說我怎么就攤上這事囱持。” “怎么了焕济?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵纷妆,是天一觀的道長。 經常有香客問我晴弃,道長掩幢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任上鞠,我火速辦了婚禮际邻,結果婚禮上,老公的妹妹穿的比我還像新娘芍阎。我一直安慰自己世曾,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布谴咸。 她就那樣靜靜地躺著轮听,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岭佳。 梳的紋絲不亂的頭發(fā)上血巍,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音珊随,去河邊找鬼述寡。 笑死柿隙,一個胖子當著我的面吹牛,可吹牛的內容都是我干的鲫凶。 我是一名探鬼主播优俘,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掀序!你這毒婦竟也來了帆焕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤不恭,失蹤者是張志新(化名)和其女友劉穎叶雹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體换吧,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡折晦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沾瓦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片满着。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贯莺,靈堂內的尸體忽然破棺而出风喇,到底是詐尸還是另有隱情,我是刑警寧澤缕探,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布魂莫,位于F島的核電站,受9級特大地震影響爹耗,放射性物質發(fā)生泄漏耙考。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一潭兽、第九天 我趴在偏房一處隱蔽的房頂上張望倦始。 院中可真熱鬧,春花似錦山卦、人聲如沸鞋邑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炫狱。三九已至,卻和暖如春剔猿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嬉荆。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工归敬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓汪茧,卻偏偏與公主長得像椅亚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舱污,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容