欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入了解Java并發(fā)AQS的獨(dú)占鎖模式

 更新時(shí)間:2022年10月11日 09:06:33   作者:JAVA旭陽  
AQS是一種提供了原子式管理同步狀態(tài)、阻塞和喚醒線程功能以及隊(duì)列模型的簡單框架。一般來說,同步工具實(shí)現(xiàn)鎖的控制分為獨(dú)占鎖和共享鎖,而AQS提供了對(duì)這兩種模式的支持。本文主要來介紹一下獨(dú)占鎖模式,需要的可以參考一下

概述

稍微對(duì)并發(fā)源碼了解的朋友都知道,很多并發(fā)工具如ReentrantLock、CountdownLatch的實(shí)現(xiàn)都是依賴AQS, 全稱AbstractQueuedSynchronizer。

AQS是一種提供了原子式管理同步狀態(tài)、阻塞和喚醒線程功能以及隊(duì)列模型的簡單框架。一般來說,同步工具實(shí)現(xiàn)鎖的控制分為獨(dú)占鎖和共享鎖,而AQS提供了對(duì)這兩種模式的支持。

獨(dú)占鎖: 也叫排他鎖,即鎖只能由一個(gè)線程獲取,若一個(gè)線程獲取了鎖,則其他想要獲取鎖的線程只能等待,直到鎖被釋放。比如說寫鎖,對(duì)于寫操作,每次只能由一個(gè)線程進(jìn)行,若多個(gè)線程同時(shí)進(jìn)行寫操作,將很可能出現(xiàn)線程安全問題,比如jdk中的ReentrantLock。

共享鎖: 鎖可以由多個(gè)線程同時(shí)獲取,鎖被獲取一次,則鎖的計(jì)數(shù)器+1。比較典型的就是讀鎖,讀操作并不會(huì)產(chǎn)生副作用,所以可以允許多個(gè)線程同時(shí)對(duì)數(shù)據(jù)進(jìn)行讀操作,而不會(huì)有線程安全問題,當(dāng)然,前提是這個(gè)過程中沒有線程在進(jìn)行寫操作,比如ReadWriteLock和CountdownLatch。

本文重點(diǎn)講解下AQS對(duì)獨(dú)占鎖模式的支持。

自定義獨(dú)占鎖例子

首先我們自定義一個(gè)非常簡單的獨(dú)占鎖同步器demo, 來了解下AQS的使用。

public class ExclusiveLock implements Lock {

    // 同步器,繼承自AQS
    private static class Sync extends AbstractQueuedSynchronizer {

        // 重寫獲取鎖的方式
        @Override
        protected boolean tryAcquire(int acquires) {
            assert acquires == 1;
            // cas的方式搶鎖
            if(compareAndSetState(0, 1)) {
                // 設(shè)置搶占鎖的線程為當(dāng)前線程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int releases) {
            assert releases == 1;

            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            };
            //設(shè)置搶占鎖的線程為null
            setExclusiveOwnerThread(null);
            // 釋放鎖
            setState(0);
            return true;
        }
    }

    private final Sync sync = new Sync();

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }
    
    @Override
    public Condition newCondition() {
        return null;
    }
}

這里是一個(gè)不可重入獨(dú)占鎖類,它使用值0表示未鎖定狀態(tài),使用值1表示鎖定狀態(tài)。

驗(yàn)證:

public static void main(String[] args) throws InterruptedException {
        ExclusiveLock exclusiveLock = new ExclusiveLock();


        new Thread(() -> {
            try {
                exclusiveLock.lock();
                System.out.println("thread1 get lock");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                exclusiveLock.unlock();
                System.out.println("thread1 release lock");
            }

        }).start();

        new Thread(() -> {
            try {
                exclusiveLock.lock();
                System.out.println("thread2 get lock");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                exclusiveLock.unlock();
                System.out.println("thread2 release lock");
            }

        }).start();

        Thread.currentThread().join();
    }

這樣一個(gè)很簡單的獨(dú)占鎖同步器就實(shí)現(xiàn)了,下面我們了解下它的核心機(jī)制。

核心原理機(jī)制

如果讓你設(shè)計(jì)一個(gè)獨(dú)占鎖你要考慮哪些方面呢?

  • 線程如何表示搶占鎖資源成功呢?是不是可以個(gè)狀態(tài)state標(biāo)記,state=1表示有線程持有鎖,其他線程等待。
  • 其他搶鎖失敗的線程維護(hù)在哪里呢?是不是要引入一個(gè)隊(duì)列維護(hù)獲取鎖失敗的線程隊(duì)列?
  • 那如何讓線程實(shí)現(xiàn)阻塞呢?還記得LockSupport.park和unpark可以實(shí)現(xiàn)線程的阻塞和喚醒嗎?

這些問題我們可以再AQS的數(shù)據(jù)結(jié)構(gòu)和源碼中統(tǒng)一找到答案。

AQS內(nèi)部維護(hù)了一個(gè)volatile int state(代表共享資源)和一個(gè)FIFO線程等待隊(duì)列(多線程爭(zhēng)用資源被阻塞時(shí)會(huì)進(jìn)入此隊(duì)列)。

以上面?zhèn)€的例子為例,state初始化為0,表示未鎖定狀態(tài)。A線程lock()時(shí),會(huì)調(diào)用AQS的acquire方法,acquire會(huì)調(diào)用子類重寫的tryAcquire()方法,通過cas的方式搶占鎖。此后,其他線程再tryAcquire()時(shí)就會(huì)失敗,進(jìn)入到CLH隊(duì)列中,直到A線程unlock()即釋放鎖為止,即將state還原為0,其它線程才有機(jī)會(huì)獲取該鎖。

AQS作為一個(gè)抽象方法,提供了加鎖、和釋放鎖的框架,這里采用的模板方模式,在上面中提到的tryAcquire、tryRelease就是和獨(dú)占模式相關(guān)的模板方法,其他的模板方法和共享鎖模式或者Condition相關(guān),本文不展開討論。

方法名描述
protected boolean tryAcquire(int arg)獨(dú)占方式。arg為獲取鎖的次數(shù),嘗試獲取資源,成功則返回True,失敗則返回False。
protected boolean tryRelease(int arg)獨(dú)占方式。arg為釋放鎖的次數(shù),嘗試釋放資源,成功則返回True,失敗則返回False。

源碼解析

上圖是AQS的類結(jié)構(gòu)圖,其中標(biāo)紅部分是組成AQS的重要成員變量。

成員變量

state共享變量

AQS中里一個(gè)很重要的字段state,表示同步狀態(tài),是由volatile修飾的,用于展示當(dāng)前臨界資源的獲鎖情況。通過getState(),setState(),compareAndSetState()三個(gè)方法進(jìn)行維護(hù)。

關(guān)于state的幾個(gè)要點(diǎn):

  • 使用volatile修飾,保證多線程間的可見性。
  • getState()、setState()、compareAndSetState()使用final修飾,限制子類不能對(duì)其重寫。
  • compareAndSetState()采用樂觀鎖思想的CAS算法,保證原子性操作。

CLH隊(duì)列(FIFO隊(duì)列)

AQS里另一個(gè)重要的概念就是CLH隊(duì)列,它是一個(gè)雙向鏈表隊(duì)列,其內(nèi)部由head和tail分別記錄頭結(jié)點(diǎn)和尾結(jié)點(diǎn),隊(duì)列的元素類型是Node。

private transient volatile Node head;
private transient volatile Node tail;

Node的結(jié)構(gòu)如下:

static final class Node {
    //共享模式下的等待標(biāo)記
    static final Node SHARED = new Node();
    //獨(dú)占模式下的等待標(biāo)記
    static final Node EXCLUSIVE = null;
    //表示當(dāng)前結(jié)點(diǎn)已取消調(diào)度。當(dāng)timeout或被中斷(響應(yīng)中斷的情況下),會(huì)觸發(fā)變更為此狀態(tài),進(jìn)入該狀態(tài)后的結(jié)點(diǎn)將不會(huì)再變化。
    static final int CANCELLED =  1;
    //表示后繼結(jié)點(diǎn)在等待當(dāng)前結(jié)點(diǎn)喚醒。后繼結(jié)點(diǎn)入隊(duì)時(shí),會(huì)將前繼結(jié)點(diǎn)的狀態(tài)更新為SIGNAL。
    static final int SIGNAL    = -1;
    //表示結(jié)點(diǎn)等待在Condition上,當(dāng)其他線程調(diào)用了Condition的signal()方法后,CONDITION狀態(tài)的結(jié)點(diǎn)將從等待隊(duì)列轉(zhuǎn)移到同步隊(duì)列中,等待獲取同步鎖。
    static final int CONDITION = -2;
    //共享模式下,前繼結(jié)點(diǎn)不僅會(huì)喚醒其后繼結(jié)點(diǎn),同時(shí)也可能會(huì)喚醒后繼的后繼結(jié)點(diǎn)。
    static final int PROPAGATE = -3;
    //狀態(tài),包括上面的四種狀態(tài)值,初始值為0,一般是節(jié)點(diǎn)的初始狀態(tài)
    volatile int waitStatus;
    //上一個(gè)節(jié)點(diǎn)的引用
    volatile Node prev;
    //下一個(gè)節(jié)點(diǎn)的引用
    volatile Node next;
    //保存在當(dāng)前節(jié)點(diǎn)的線程引用
    volatile Thread thread;
    //condition隊(duì)列的后續(xù)節(jié)點(diǎn)
    Node nextWaiter;
}

注意,waitSstatus負(fù)值表示結(jié)點(diǎn)處于有效等待狀態(tài),而正值表示結(jié)點(diǎn)已被取消。所以源碼中很多地方用>0、<0來判斷結(jié)點(diǎn)的狀態(tài)是否正常。

exclusiveOwnerThread

AQS通過繼承AbstractOwnableSynchronizer類,擁有的屬性。表示獨(dú)占模式下同步器持有的線程。

獨(dú)占鎖獲取acquire(int)

acquire(int)是獨(dú)占模式下線程獲取共享資源的入口方法。

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

方法的整體流程如下:

  • tryAcquire()嘗試直接去獲取資源,如果成功則直接返回。
  • 如果失敗則調(diào)用addWaiter()方法把當(dāng)前線程包裝成Node(狀態(tài)為EXCLUSIVE,標(biāo)記為獨(dú)占模式)插入到CLH隊(duì)列末尾。
  • acquireQueued()方法使線程阻塞在等待隊(duì)列中獲取資源,一直獲取到資源后才返回,如果在整個(gè)等待過程中被中斷過,則返回true,否則返回false。
  • 線程在等待過程中被中斷過,它是不響應(yīng)的。只有線程獲取到資源后,acquireQueued返回true,響應(yīng)中斷。

tryAcquire(int)

此方法嘗試去獲取獨(dú)占資源。如果獲取成功,則直接返回true,否則直接返回false。

//直接拋出異常,這是由子類進(jìn)行實(shí)現(xiàn)的方法,體現(xiàn)了模板模式的思想
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

AQS只是一個(gè)框架,具體資源的獲取/釋放方式交由自定義同步器去實(shí)現(xiàn),比如公平鎖有公平鎖的獲取方式,非公平鎖有非公平鎖的獲取方式。

addWaiter(Node)

此方法用于將當(dāng)前線程加入到等待隊(duì)列的隊(duì)尾。

// 將線程封裝成一個(gè)節(jié)點(diǎn),放入同步隊(duì)列的尾部
private Node addWaiter(Node mode) {
    // 當(dāng)前線程封裝成同步隊(duì)列的一個(gè)節(jié)點(diǎn)Node
    Node node = new Node(Thread.currentThread(), mode);
    // 這個(gè)節(jié)點(diǎn)需要插入到原尾節(jié)點(diǎn)的后面,所以我們?cè)谶@里先記下原來的尾節(jié)點(diǎn)
    Node pred = tail;
    // 判斷尾節(jié)點(diǎn)是否為空,若為空表示隊(duì)列中還沒有節(jié)點(diǎn),則不執(zhí)行以下步驟
    if (pred != null) {
        // 記錄新節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)為原尾節(jié)點(diǎn)
        node.prev = pred;
        // 將新節(jié)點(diǎn)設(shè)置為新尾節(jié)點(diǎn),使用CAS操作保證了原子性
        if (compareAndSetTail(pred, node)) {
            // 若設(shè)置成功,則讓原來的尾節(jié)點(diǎn)的next指向新尾節(jié)點(diǎn)
            pred.next = node;
            return node;
        }
    }
    // 若以上操作失敗,則調(diào)用enq方法繼續(xù)嘗試(enq方法見下面)
    enq(node);
    return node;
}

private Node enq(final Node node) {
    // 使用死循環(huán)不斷嘗試
    for (;;) {
        // 記錄原尾節(jié)點(diǎn)
        Node t = tail;
        // 若原尾節(jié)點(diǎn)為空,則必須先初始化同步隊(duì)列,初始化之后,下一次循環(huán)會(huì)將新節(jié)點(diǎn)加入隊(duì)列
        if (t == null) { 
            // 使用CAS設(shè)置創(chuàng)建一個(gè)默認(rèn)的節(jié)點(diǎn)作為首屆點(diǎn)
            if (compareAndSetHead(new Node()))
                // 首尾指向同一個(gè)節(jié)點(diǎn)
                tail = head;
        } else {
            // 以下操作與addWaiter方法中的if語句塊內(nèi)一致
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

它的執(zhí)行過程大致可以總結(jié)為:將新線程封裝成一個(gè)節(jié)點(diǎn),加入到同步隊(duì)列的尾部,若同步隊(duì)列為空,則先在其中加入一個(gè)默認(rèn)的節(jié)點(diǎn),再進(jìn)行加入;若加入失敗,則使用死循環(huán)(也叫自旋)不斷嘗試,直到成功為止。

acquireQueued(Node, int)

通過tryAcquire()和addWaiter(),該線程獲取資源失敗,已經(jīng)被放入等待隊(duì)列尾部了。接下來要干嘛呢?

進(jìn)入等待狀態(tài)休息,直到其他線程徹底釋放資源后喚醒自己,自己再拿到資源,然后就可以去干自己想干的事了??梢韵胂蟪舍t(yī)院排隊(duì)拿號(hào),在等待隊(duì)列中排隊(duì)拿號(hào)(中間沒其它事干可以休息),直到拿到號(hào)后再返回。

final boolean acquireQueued(final Node node, int arg) {
    //標(biāo)記是否成功拿到資源
    boolean failed = true;
    try {
        //標(biāo)記等待過程中是否被中斷過
        boolean interrupted = false;

        //“自旋”!
        for (;;) {
            //拿到前驅(qū)
            final Node p = node.predecessor();
            //如果前驅(qū)是head,即該結(jié)點(diǎn)已成老二,那么便有資格去嘗試獲取資源(可能是老大釋放完資源喚醒自己的,當(dāng)然也可能被interrupt了)。
            if (p == head && tryAcquire(arg)) {
                //拿到資源后,將head指向該結(jié)點(diǎn)。所以head所指的標(biāo)桿結(jié)點(diǎn),就是當(dāng)前獲取到資源的那個(gè)結(jié)點(diǎn)或null。
                setHead(node);
                // setHead中node.prev已置為null,此處再將head.next置為null,就是為了方便GC回收以前的head結(jié)點(diǎn)。也就意味著之前拿完資源的結(jié)點(diǎn)出隊(duì)了!
                p.next = null; 
                 // 成功獲取資源
                failed = false;
                //返回等待過程中是否被中斷過
                return interrupted;
            }

            //如果自己可以休息了,就通過park()進(jìn)入waiting狀態(tài),直到被unpark()。
            // 如果不可中斷的情況下被中斷了,那么會(huì)從park()中醒過來,發(fā)現(xiàn)拿不到資源,從而繼續(xù)進(jìn)入park()等待。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //如果等待過程中被中斷過,哪怕只有那么一次,就將interrupted標(biāo)記為true
                interrupted = true;
        }
    } finally {
        if (failed)
             // 如果等待過程中沒有成功獲取資源(如timeout,或者可中斷的情況下被中斷了),那么取消結(jié)點(diǎn)在隊(duì)列中的等待。
            cancelAcquire(node);
    }
}

小結(jié)一下:讓線程在同步隊(duì)列中阻塞,直到它成為頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),被頭節(jié)點(diǎn)對(duì)應(yīng)的線程喚醒,然后開始獲取鎖,若獲取成功才會(huì)從方法中返回。這個(gè)方法會(huì)返回一個(gè)boolean值,表示這個(gè)正在同步隊(duì)列中的線程是否被中斷。

shouldParkAfterFailedAcquire()

此方法主要用于檢查狀態(tài),看看自己是否真的可以去休息了。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //拿到前驅(qū)的狀態(tài)
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        //如果已經(jīng)告訴前驅(qū)拿完號(hào)后通知自己一下,那就可以安心休息了
        return true;
    if (ws > 0) {
        /*
         * 如果前驅(qū)放棄了,那就一直往前找,直到找到最近一個(gè)正常等待的狀態(tài),并排在它的后邊。
         * 注意:那些放棄的結(jié)點(diǎn),由于被自己“加塞”到它們前邊,它們相當(dāng)于形成一個(gè)無引用鏈,稍后就會(huì)被保安大叔趕走了(GC回收)!
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
         //如果前驅(qū)正常,那就把前驅(qū)的狀態(tài)設(shè)置成SIGNAL,告訴它拿完號(hào)后通知自己一下。有可能失敗,人家說不定剛剛釋放完呢!
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

整個(gè)流程中,如果前驅(qū)結(jié)點(diǎn)的狀態(tài)不是SIGNAL,那么自己就不能安心去休息,需要去找個(gè)安心的休息點(diǎn),同時(shí)可以再嘗試下看有沒有機(jī)會(huì)輪到自己拿號(hào)。

parkAndCheckInterrupt()

這個(gè)方法是真正實(shí)現(xiàn)線程阻塞,休息的地方。

private final boolean parkAndCheckInterrupt() {
    // 調(diào)用park()使線程進(jìn)入waiting狀態(tài)
    LockSupport.park(this);
    //調(diào)用park()使線程進(jìn)入waiting狀態(tài)
    return Thread.interrupted();
}

park()會(huì)讓當(dāng)前線程進(jìn)入waiting狀態(tài)。在此狀態(tài)下,有兩種途徑可以喚醒該線程:1)被unpark();2)被interrupt()。

selfInterrupt()

static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

中斷線程,設(shè)置線程的中斷位true。因?yàn)閜arkAndCheckInterrupt方法中的Thread.interrupted()會(huì)清楚中斷標(biāo)記,需要在selfInterrupt方法中將中斷補(bǔ)上。

整個(gè)流程可以用下面一個(gè)圖來說明。

獨(dú)占鎖釋放release(int)

release(int)是獨(dú)占模式下線程釋放共享資源的入口。它會(huì)釋放指定量的資源,如果徹底釋放了(即state=0),它會(huì)喚醒等待隊(duì)列里的其他線程來獲取資源。

public final boolean release(int arg) {
	// 上邊自定義的tryRelease如果返回true,說明該鎖沒有被任何線程持有
	if (tryRelease(arg)) {
		// 獲取頭結(jié)點(diǎn)
		Node h = head;
		// 頭結(jié)點(diǎn)不為空并且頭結(jié)點(diǎn)的waitStatus不是初始化節(jié)點(diǎn)情況,解除線程掛起狀態(tài)
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);
		return true;
	}
	return false;
}

這里的判斷條件為什么是h != null && h.waitStatus != 0?

  • h == null Head還沒初始化。初始情況下,head == null,第一個(gè)節(jié)點(diǎn)入隊(duì),Head會(huì)被初始化一個(gè)虛擬節(jié)點(diǎn)。所以說,這里如果還沒來得及入隊(duì),就會(huì)出現(xiàn)head == null 的情況。
  • h != null && waitStatus == 0 表明后繼節(jié)點(diǎn)對(duì)應(yīng)的線程仍在運(yùn)行中,不需要喚醒。
  • h != null && waitStatus < 0 表明后繼節(jié)點(diǎn)可能被阻塞了,需要喚醒。

tryRelease(int)

tryRelease是一個(gè)模板方法,由子類實(shí)現(xiàn),定義釋放鎖的邏輯。

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

因?yàn)檫@是獨(dú)占模式,該線程來釋放資源,那么它肯定已經(jīng)拿到獨(dú)占資源了,直接減掉相應(yīng)量的資源即可(state-=arg),也不需要考慮線程安全的問題。但要注意它的返回值,上面已經(jīng)提到了,release()是根據(jù)tryRelease()的返回值來判斷該線程是否已經(jīng)完成釋放掉資源了!所以自義定同步器在實(shí)現(xiàn)時(shí),如果已經(jīng)徹底釋放資源(state=0),要返回true,否則返回false。

unparkSuccessor(Node)

private void unparkSuccessor(Node node) {
	// 獲取頭結(jié)點(diǎn)waitStatus
	int ws = node.waitStatus;
	if (ws < 0)
		compareAndSetWaitStatus(node, ws, 0);
	// 獲取當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
	Node s = node.next;
	// 如果下個(gè)節(jié)點(diǎn)是null或者下個(gè)節(jié)點(diǎn)被cancelled,就找到隊(duì)列最開始的非cancelled的節(jié)點(diǎn)
	if (s == null || s.waitStatus > 0) {
		s = null;
		// 就從尾部節(jié)點(diǎn)開始找,到隊(duì)首,找到隊(duì)列第一個(gè)waitStatus<0的節(jié)點(diǎn)。
		for (Node t = tail; t != null && t != node; t = t.prev)
			if (t.waitStatus <= 0)
				s = t;
	}
	// 如果當(dāng)前節(jié)點(diǎn)的下個(gè)節(jié)點(diǎn)不為空,而且狀態(tài)<=0,就把當(dāng)前節(jié)點(diǎn)unpark
	if (s != null)
		LockSupport.unpark(s.thread);
}

為什么要從后往前找第一個(gè)非Cancelled的節(jié)點(diǎn)呢?

之前的addWaiter方法:

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

我們從這里可以看到,節(jié)點(diǎn)入隊(duì)并不是原子操作,也就是說,node.prev = pred; compareAndSetTail(pred, node) 這兩個(gè)地方可以看作Tail入隊(duì)的原子操作,但是此時(shí)pred.next = node;還沒執(zhí)行,如果這個(gè)時(shí)候執(zhí)行了unparkSuccessor方法,就沒辦法從前往后找了,所以需要從后往前找。還有一點(diǎn)原因,在產(chǎn)生CANCELLED狀態(tài)節(jié)點(diǎn)的時(shí)候,先斷開的是Next指針,Prev指針并未斷開,因此也是必須要從后往前遍歷才能夠遍歷完全部的Node。

綜上所述,如果是從前往后找,由于極端情況下入隊(duì)的非原子操作和CANCELLED節(jié)點(diǎn)產(chǎn)生過程中斷開Next指針的操作,可能會(huì)導(dǎo)致無法遍歷所有的節(jié)點(diǎn)。所以,喚醒對(duì)應(yīng)的線程后,對(duì)應(yīng)的線程就會(huì)繼續(xù)往下執(zhí)行。

總結(jié)

本文主要講解了AQS的獨(dú)占模式,最關(guān)鍵的是acquire()和release這兩個(gè)和獨(dú)占息息相關(guān)的方法,同時(shí)通過一個(gè)自定義簡單的demo幫助大家深入淺出的理解,其實(shí)AQS的功能不限于此,內(nèi)容很多,這里就先分享一個(gè)最基礎(chǔ)獨(dú)占鎖的原理,希望對(duì)大家有幫助。

以上就是深入了解Java并發(fā)AQS的獨(dú)占鎖模式的詳細(xì)內(nèi)容,更多關(guān)于Java并發(fā)AQS獨(dú)占鎖模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 使用java + selenium + OpenCV破解騰訊防水墻滑動(dòng)驗(yàn)證碼功能

    使用java + selenium + OpenCV破解騰訊防水墻滑動(dòng)驗(yàn)證碼功能

    這篇文章主要介紹了使用java + selenium + OpenCV破解騰訊防水墻滑動(dòng)驗(yàn)證碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Java如何獲取屬性的注釋信息詳解

    Java如何獲取屬性的注釋信息詳解

    Java注解是從Java5開始添加到Java的,這篇文章主要給大家介紹了關(guān)于Java如何獲取屬性的注釋信息的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考下
    2021-07-07
  • 基于Map的computeIfAbsent的使用場(chǎng)景和使用方式

    基于Map的computeIfAbsent的使用場(chǎng)景和使用方式

    這篇文章主要介紹了基于Map的computeIfAbsent的使用場(chǎng)景和使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java面試題之HashMap 的 hash 方法原理是什么

    Java面試題之HashMap 的 hash 方法原理是什么

    那天,小二去蔚來面試,面試官老王一上來就問他:HashMap 的 hash 方法的原理是什么?當(dāng)時(shí)就把裸面的小二給蚌埠住了,這篇文章將詳細(xì)解答該題目
    2021-11-11
  • Jenkins+maven持續(xù)集成的實(shí)現(xiàn)

    Jenkins+maven持續(xù)集成的實(shí)現(xiàn)

    這篇文章主要介紹了Jenkins+maven持續(xù)集成的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Maven使用方法詳及方式詳細(xì)介紹

    Maven使用方法詳及方式詳細(xì)介紹

    使用maven倉庫的話需要從網(wǎng)上下載maven的包,比如“apache-maven-3.5.4-bin.tar”,下載完成之后解壓,在解壓的文件夾中的conf目錄下的settings.xml文件夾下就可以配置maven遠(yuǎn)程倉庫和本地倉庫的地址
    2022-11-11
  • 關(guān)于MVC的dao層、service層和controller層詳解

    關(guān)于MVC的dao層、service層和controller層詳解

    這篇文章主要介紹了關(guān)于MVC的dao層、service層和controller層詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • 一文看懂springboot實(shí)現(xiàn)短信服務(wù)功能

    一文看懂springboot實(shí)現(xiàn)短信服務(wù)功能

    項(xiàng)目中的短信服務(wù)基本上上都會(huì)用到,簡單的注冊(cè)驗(yàn)證碼,消息通知等等都會(huì)用到。這篇文章主要介紹了springboot 實(shí)現(xiàn)短信服務(wù)功能,需要的朋友可以參考下
    2019-10-10
  • Java報(bào)錯(cuò):UnsupportedOperationException in Collections的解決方案

    Java報(bào)錯(cuò):UnsupportedOperationException in Collection

    在Java編程中,UnsupportedOperationException是一種常見的運(yùn)行時(shí)異常,通常在試圖對(duì)不支持的操作執(zhí)行修改時(shí)發(fā)生,它表示當(dāng)前操作不被支持,本文將深入探討UnsupportedOperationException的產(chǎn)生原因,并提供具體的解決方案和最佳實(shí)踐,需要的朋友可以參考下
    2024-06-06
  • Springboot實(shí)現(xiàn)接口傳輸加解密的步驟詳解

    Springboot實(shí)現(xiàn)接口傳輸加解密的步驟詳解

    這篇文章主要給大家詳細(xì)介紹了Springboot實(shí)現(xiàn)接口傳輸加解密的操作步驟,文中有詳細(xì)的圖文解釋和代碼示例供大家參考,對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-09-09

最新評(píng)論