精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Java面試中AQS最全面解析

開(kāi)發(fā) 前端
線程如果搶到鎖自然順利往下運(yùn)行了,而那些沒(méi)有搶到鎖的線程怎么處理呢?如果一直處于活躍狀態(tài),cpu肯定是吃不消,那就需要掛起。具體又如何掛起呢?

前置思考

實(shí)現(xiàn)鎖應(yīng)該考慮的問(wèn)題:

  1. 如何獲取資源(鎖)?
  2. 獲取不到資源的線程如何處理?
  3. 如何釋放資源?
  4. 資源釋放后如何讓其他線程獲取資源?

由此可以得出實(shí)現(xiàn)一把鎖,應(yīng)該具備哪些邏輯:

  • 鎖的標(biāo)識(shí)
    需要有個(gè)標(biāo)識(shí)或者狀態(tài)來(lái)表示鎖是否已經(jīng)被占用。
  • 線程搶鎖的邏輯
    多個(gè)線程如何搶鎖,如何才算搶到鎖,已經(jīng)搶到鎖的線程再次搶鎖如何處理等等。
  • 線程掛起的邏輯
    線程如果搶到鎖自然順利往下運(yùn)行了,而那些沒(méi)有搶到鎖的線程怎么處理呢?如果一直處于活躍狀態(tài),cpu肯定是吃不消,那就需要掛起。具體又如何掛起呢?
  • 線程存儲(chǔ)機(jī)制
    沒(méi)有搶到鎖的線程就掛起了,而且被掛起的線程可能有很多個(gè),這些線程總要放在某個(gè)地方保存起來(lái)等待喚醒,然而這么多被掛起的線程,要喚醒哪一個(gè)呢?這就需要一套保存機(jī)制來(lái)支撐喚醒邏輯。
  • 線程釋放鎖的邏輯
    線程在執(zhí)行完后就要釋放鎖,跟搶鎖邏輯是對(duì)應(yīng)的,其實(shí)也是操作鎖標(biāo)識(shí)。
  • 線程喚醒的邏輯
    鎖釋放后,就要去喚醒被阻塞的線程,這就要考慮喚醒誰(shuí),如何喚醒,喚醒后的線程做什么事情。

帶著上面的思考,我們來(lái)看看AQS是怎么處理的。

AQS由來(lái)

在最早期java中的同步機(jī)制是通過(guò)關(guān)鍵字synchronized實(shí)現(xiàn),這個(gè)鎖是java原生的,jvm層面實(shí)現(xiàn)的。在1.6之前synchronized的性能比較低,是一把純重量級(jí)鎖。

后來(lái),Doug Lea開(kāi)發(fā)并引入了java.util.concurrent包,這個(gè)包基本涵蓋了java并發(fā)操作的半壁江山,該包內(nèi)的并發(fā)工具類基本是以AQS為基礎(chǔ)的,AQS提高了同步操作的性能,在性能上遠(yuǎn)超當(dāng)時(shí)的synchronized,后來(lái)synchronized做了優(yōu)化,java1.6及之后兩者的性能就差不多了。

AQS是什么

AQS的全稱為AbstractQueuedSynchronizer。

AQS其實(shí)是一個(gè)抽象類,它實(shí)現(xiàn)了線程掛起的邏輯,實(shí)現(xiàn)了線程存儲(chǔ)機(jī)制,實(shí)現(xiàn)了鎖的狀態(tài)邏輯,實(shí)現(xiàn)了線程喚醒的邏輯,卻只定義了線程搶鎖和釋放鎖的抽象,這樣做的目的是將搶鎖和釋放鎖的邏輯交給子類來(lái)實(shí)現(xiàn),這樣有助于實(shí)現(xiàn)各種不同特性的鎖,比如共享鎖,獨(dú)占鎖,公平鎖,非公平鎖,可重入等。并且以模板方法模式將上述上鎖流程和釋放鎖流程封裝為固定模板方法。所以AQS就是一個(gè)多線程訪問(wèn)共享資源的同步器框架。

AQS實(shí)現(xiàn)同步機(jī)制有兩種模式,一種是獨(dú)占模式,一種是共享模式。兩種模式分別提供提供兩個(gè)模板方法實(shí)現(xiàn)。四個(gè)模板方法為acquire,release,acquireShared,releaseShared。

  • 獨(dú)占模式的鎖是只允許一個(gè)線程持有鎖
  • 共享模式的鎖是允許多余一個(gè)的線程持有鎖
  • 接下來(lái)分別介紹這四個(gè)方法的邏輯

acquire方法解析

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

acquire方法是獨(dú)占模式上鎖的整個(gè)邏輯,這個(gè)方法是一個(gè)模板方法,其中的tryAcquire是獲取鎖的邏輯,這個(gè)方法是一個(gè)抽象方法,由具體的子類實(shí)現(xiàn),如何獲取鎖,怎樣才算獲取到鎖這些問(wèn)題子類自己決定,AQS不做處理。

addWaiter方法負(fù)責(zé)是線程存儲(chǔ)的邏輯,aqs里面存儲(chǔ)機(jī)制的核心是兩個(gè)隊(duì)列,等待隊(duì)列和條件隊(duì)列,它們用來(lái)保存被阻塞的線程,在這個(gè)方法中通過(guò)cas+自旋的方式將線程添加到等待隊(duì)列中。

先來(lái)介紹等待隊(duì)列,等待隊(duì)列的結(jié)構(gòu)如下:

圖片圖片

等待隊(duì)列是一個(gè)雙向鏈表,每個(gè)節(jié)點(diǎn)就是一個(gè)node對(duì)象,node是aqs類中的一個(gè)靜態(tài)內(nèi)部類,它的屬性如下:

  • node{thread;prev;next;nextWaiter;waitStatus;}
  • thread是當(dāng)前node節(jié)點(diǎn)所綁定的線程;
  • prev是前置節(jié)點(diǎn)的引用;
  • next是后置節(jié)點(diǎn)的引用;
  • nextWaiter如果是等待隊(duì)列節(jié)點(diǎn)就標(biāo)示獨(dú)占模式節(jié)點(diǎn)還是共享模式,如果是條件隊(duì)列節(jié)點(diǎn)就作為后置節(jié)點(diǎn)指針;

waitStatus是節(jié)點(diǎn)的狀態(tài),其狀態(tài)值如下:

  • static final int CANCELLED =  1; 出現(xiàn)異常
  • static final int SIGNAL    = -1;可被喚醒
  • static final int CONDITION = -2; 條件等待
  • static final int PROPAGATE = -3;傳播

AQS類自身也有幾個(gè)比較重要的屬性:

//正在持有鎖的線程
private transient Thread exclusiveOwnerThread;
//等待隊(duì)列的頭節(jié)點(diǎn)
private transient volatile Node head;
//等待隊(duì)列的尾節(jié)點(diǎn)
private transient volatile Node tail;
//鎖標(biāo)識(shí)字段
private volatile int state;

了解了等待隊(duì)列,接下來(lái)具體看看addWaiter方法的邏輯;

  1. 首先如果隊(duì)列還沒(méi)有初始化會(huì)先初始化隊(duì)列,初始化就是先創(chuàng)建一個(gè)空的node節(jié)點(diǎn),把a(bǔ)qs里面的head和tail屬性指向這個(gè)空的node,初始化完成;

圖片圖片

  1. 先創(chuàng)建一個(gè)node節(jié)點(diǎn),默認(rèn)屬性如下:

node{ thread=當(dāng)前線程t1;prev;next;nextWaiter=獨(dú)占模式;waitStatus=0}

開(kāi)始入隊(duì)操作,入隊(duì)就是cas+自旋的方式將tail指針指向新加入的node節(jié)點(diǎn),并且把新加入的node和head建立雙向指針。

圖片圖片

cas是保證原子性的,多線程操作的情況下,當(dāng)前線程可能會(huì)操作失敗,自旋是為了失敗重試,保證一定能夠入隊(duì)成功。

入隊(duì)成功后,就要掛起線程了,acquireQueued方法就是掛起操作。

這個(gè)方法比較核心,線程掛起的邏輯和線程喚醒后的邏輯都在此方法中,源碼如下:

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

邏輯解析:

  1. 開(kāi)啟for循環(huán),讓線程處于循環(huán)中
  2. node節(jié)點(diǎn)已經(jīng)入隊(duì),先拿到node節(jié)點(diǎn)的前置節(jié)點(diǎn),然后做如下判斷
if (p == head && tryAcquire(arg))

上面介紹了等待隊(duì)列,等待隊(duì)列的head節(jié)點(diǎn)永遠(yuǎn)是一個(gè)不綁定線程的節(jié)點(diǎn),所以拿到前置節(jié)點(diǎn)后判斷是否為head節(jié)點(diǎn),如果為head節(jié)點(diǎn)才有資格再次獲取鎖,可以發(fā)現(xiàn)如果隊(duì)列中已經(jīng)有其他線程處于阻塞等待狀態(tài),新入隊(duì)線程是在這個(gè)判斷中永遠(yuǎn)會(huì)返回fasle。

這個(gè)判斷加在這里有什么用處呢?

有兩個(gè)用處:第一個(gè)是入隊(duì)后掛起前這個(gè)時(shí)間段中,可能鎖已經(jīng)被釋放了,所以這里再次嘗試獲取鎖,這樣就不用阻塞掛起了;第二個(gè)用處是,這個(gè)判斷處于循環(huán)中,阻塞掛起的動(dòng)作也是在循環(huán)中,當(dāng)被喚醒后,線程會(huì)從被掛起的點(diǎn)繼續(xù)運(yùn)行,會(huì)再次進(jìn)入這個(gè)判斷,從而實(shí)現(xiàn)被喚醒的線程再次嘗試換取鎖的邏輯。

  1. 如果沒(méi)有獲取到鎖,那接下來(lái)就會(huì)進(jìn)入這個(gè)方法shouldParkAfterFailedAcquire,這個(gè)方法的源碼如下
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

代碼邏輯為:獲取node節(jié)點(diǎn)的前置節(jié)點(diǎn)的waitStatus屬性;

如果waitStatus為-1返回true;

如果waitStatus>0,根據(jù)waitStatus狀態(tài)可知,大于0的只有1,1代表線程被取消或者線程異常,所以這里的做法是將異常的node節(jié)點(diǎn)從隊(duì)列中移除,采用的方式為從尾節(jié)點(diǎn)開(kāi)始向前遍歷判斷移除,直到遇到一個(gè)非異常節(jié)點(diǎn)。返回false。

如果waitStatus小于-1,那就把waitStatus通過(guò)cas改為-1,返回false。

如果此方法返回false,因?yàn)楫?dāng)前處在循環(huán)中,所以會(huì)再次進(jìn)入此方法,此時(shí)一定會(huì)返回true。

只有將當(dāng)前node節(jié)點(diǎn)的前置節(jié)點(diǎn)設(shè)置為-1后,此方法才會(huì)返回true,從而會(huì)進(jìn)入后面的parkAndCheckInterrupt()方法,這個(gè)方法就很簡(jiǎn)單了,就是調(diào)用LockSupport類的park方法將線程阻塞掛起。

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
 }

為什么在阻塞前一定要將當(dāng)前node節(jié)點(diǎn)的前置節(jié)點(diǎn)置為-1?

waitStatus為-1代表可喚醒狀態(tài),獨(dú)占模式下,AQS在喚醒被阻塞線程的時(shí)候,總是通過(guò)判斷head節(jié)點(diǎn)的waitStatus狀態(tài),如果為可喚醒狀態(tài)代表head后面的節(jié)點(diǎn)可以被喚醒,否則不允許喚醒。

這樣做的好處是,當(dāng)head節(jié)點(diǎn)后面線程獲取到鎖并出隊(duì)后,可以直接將head指針移動(dòng)到第一個(gè)線程節(jié)點(diǎn),然后將此節(jié)點(diǎn)上的前置指針刪除,將線程屬性刪除,作為新的head節(jié)點(diǎn)。

圖片圖片

當(dāng)線程調(diào)用park方法后,線程就阻塞在這里,當(dāng)被喚醒后,線程也是從這個(gè)點(diǎn)繼續(xù)往下進(jìn)行,此時(shí)依然處在循環(huán)中,這個(gè)時(shí)候會(huì)開(kāi)始新一輪循環(huán),從而再次進(jìn)入嘗試獲取鎖的判斷,如果獲取到鎖,就出隊(duì),否則再次進(jìn)入阻塞掛起的方法進(jìn)行掛起操作。

這里的設(shè)計(jì)是先搶鎖,搶到鎖后再出隊(duì),避免在沒(méi)有搶到鎖的情況下不用再次入隊(duì)造成的時(shí)間消耗。

release方法解析

//獨(dú)占模式的鎖調(diào)用的釋放鎖邏輯    
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

這個(gè)方法也是一個(gè)模板方法,tryRelease是釋放鎖的方法,它是抽象方法,具體由子類來(lái)實(shí)現(xiàn)。

釋放成功后就要喚醒被阻塞的線程,核心邏輯在下面這個(gè)方法中,源碼如下:

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)
            LockSupport.unpark(s.thread);
    }

先看下整體邏輯,這兩段代碼的邏輯其實(shí)很簡(jiǎn)單:

  1. head節(jié)點(diǎn)的waitStatus屬性為-1,才能進(jìn)入unparkSuccessor進(jìn)行喚醒邏輯
  2. 在unparkSuccessor方法中首先會(huì)將head節(jié)點(diǎn)的waitStatus改為0
  3. 取head節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)next,要判斷next節(jié)點(diǎn)的waitStatus屬性是否大于0,如果大于0表示此節(jié)點(diǎn)異?;蛘弑蝗∠麑儆诜钦9?jié)點(diǎn),從尾節(jié)點(diǎn)向前遍歷直到找到最靠近head節(jié)點(diǎn)的正常節(jié)點(diǎn),即為要喚醒的線程。
  4. 最后調(diào)用LockSupport.unpark方法喚醒線程。

邏輯很容能看懂,但是這里有個(gè)問(wèn)題,為什么前面有這段代碼

if (h != null && h.waitStatus != 0) 
       unparkSuccessor(h);

后面unparkSuccessor方法又有這一段代碼

if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

不難看出邏輯是waitStatus不為0進(jìn)入unparkSuccessor方法,進(jìn)入方法馬上把waitStatus改為0,這是在阻止后續(xù)的線程再進(jìn)來(lái)。

那真正的用意是什么呢?

通過(guò)上面代碼可以知道釋放鎖邏輯和喚醒邏輯是分開(kāi)的,看下面的時(shí)間抽

  1. 線程1搶到鎖
  2. 線程1釋放鎖
  3. 線程2搶到鎖
  4. 線程1判斷head節(jié)點(diǎn)waitStatus狀態(tài)為-1后,進(jìn)入unparkSuccessor方法執(zhí)行喚醒操作,該方法第一步是將waitStatus狀態(tài)改為0
  5. 線程2釋放鎖
  6. 線程2判斷head節(jié)點(diǎn)waitStatus狀態(tài)為0后,不會(huì)進(jìn)入unparkSuccessor方法

上面這個(gè)場(chǎng)景是非公平鎖的場(chǎng)景,公平鎖說(shuō)的是所有線程都要按照順序排隊(duì)獲取鎖,而非公平鎖說(shuō)的是新進(jìn)來(lái)的線程可以和剛被喚醒的線程搶鎖。

在非公平鎖的場(chǎng)景中,如果代碼塊中的邏輯執(zhí)行的足夠快就有可能發(fā)生上面的情況,線程1和線程2都是都去喚醒同一個(gè)線程,所以這里通過(guò)將head節(jié)點(diǎn)的waitStatus改為0的方式將其他線程拒之門(mén)外,這樣就保證在head節(jié)點(diǎn)后面的線程只會(huì)由一個(gè)線程去喚醒。

acquireShared方法解析

//共享模式的鎖調(diào)用的上鎖邏輯   
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

此方法同樣是一個(gè)模板方法,tryAcquireShared方法是抽象方法,供子類實(shí)現(xiàn)搶鎖的邏輯,doAcquireShared方法則是實(shí)現(xiàn)阻塞掛起和入隊(duì),doAcquireShared方法源碼如下:

private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head;
        setHead(node);
      
        if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

通過(guò)源碼會(huì)發(fā)現(xiàn)doAcquireShared這個(gè)方法合并了入隊(duì)和掛起兩個(gè)步驟,整體的邏輯基本和獨(dú)占模式一樣,接下來(lái)只介紹不同的地方。

第一個(gè)不同,入隊(duì)的時(shí)候創(chuàng)建的node節(jié)點(diǎn)為共享模式節(jié)點(diǎn),即nextWaiter屬性的值不同。

第二個(gè)不同,獨(dú)占模式下線程被喚醒重新獲取到鎖后,就要出隊(duì)了,而共享模式下除了出隊(duì),還會(huì)判斷是否資源充足,如果充足就喚醒下一個(gè)節(jié)點(diǎn)。

releaseShared方法解析

//共享模式的鎖調(diào)用的釋放鎖的邏輯   
public final boolean releaseShared(int arg) {
      if (tryReleaseShared(arg)) {
          doReleaseShared();
          return true;
      }
      return false;
}

同樣此方法也是模板方法,tryReleaseShared方法是交給子類實(shí)現(xiàn)的釋放鎖的邏輯,doReleaseShared方法則是aqs自己實(shí)現(xiàn)的喚醒邏輯,喚醒邏輯和獨(dú)占模式下的喚醒邏輯大同小異,都是喚醒head節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)綁定的線程,不再過(guò)多贅述。

總結(jié)一下獨(dú)占和共享模式在aqs中實(shí)現(xiàn)的最大不同是被喚醒的線程出隊(duì)后會(huì)在資源充足的情況下順便喚醒其后面節(jié)點(diǎn)的線程。

AQS中的Condition

上面說(shuō)過(guò),AQS有兩個(gè)隊(duì)列,等待隊(duì)列和條件隊(duì)列,上面介紹了等待隊(duì)列,但是條件隊(duì)列一直未提,那么條件隊(duì)列是做什么的呢?

先說(shuō)下條件隊(duì)列的結(jié)構(gòu);

AQS內(nèi)部有一個(gè)內(nèi)部類ConditionObject,其內(nèi)部維護(hù)了一個(gè)單向鏈表(先進(jìn)先出),這個(gè)內(nèi)部類內(nèi)有兩個(gè)屬性:firstWaiter和lastWaiter分別指向單向鏈表的頭結(jié)點(diǎn)和尾節(jié)點(diǎn),這個(gè)單向鏈表就是條件隊(duì)列,和等待隊(duì)列的不同處是它的頭節(jié)點(diǎn)是綁定線程的,條件隊(duì)列的結(jié)構(gòu)如下:

圖片圖片

這個(gè)內(nèi)部類主要的方法是如下三個(gè),這里直接說(shuō)每個(gè)方法的底層邏輯,源碼就不展示了,可以自己去查閱源碼。

首先先說(shuō)下Condition整體的思維邏輯。

  1. 入隊(duì),包括初始化條件隊(duì)列,隊(duì)列的節(jié)點(diǎn)依然是node對(duì)象,利用nextWaiter屬性指向下一個(gè)節(jié)點(diǎn),waitStatus屬性的值默認(rèn)為-2,代表等待。
  2. 釋放鎖,在入隊(duì)后就要釋放鎖了。
  3. 阻塞
  4. 條件達(dá)成后換隊(duì)。
  5. 阻塞被喚醒后,按照獨(dú)占鎖的方式去再次嘗試搶鎖嗎,這里和獨(dú)占模式下的喚醒邏輯是一樣的。

await()的邏輯;

  1. 入隊(duì),包括初始化條件隊(duì)列,隊(duì)列的節(jié)點(diǎn)依然是node對(duì)象,利用nextWaiter屬性指向下一個(gè)節(jié)點(diǎn),waitStatus屬性的值默認(rèn)為-2,代表等待。
  2. 釋放鎖,在入隊(duì)后就要釋放鎖了。
  3. 阻塞。

signal()的邏輯;

  1. 條件達(dá)成后換隊(duì)
  2. 阻塞被喚醒后,按照獨(dú)占鎖的方式去再次嘗試搶鎖嗎,這里和獨(dú)占模式下的喚醒邏輯是一樣的。

條件達(dá)成后換隊(duì)的意思就是將條件隊(duì)里的頭節(jié)點(diǎn)移動(dòng)到獨(dú)占模式的等待隊(duì)列中去,入隊(duì)的方式和獨(dú)占模式下入隊(duì)方式一樣,入隊(duì)之后會(huì)將當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的waitStatus置為-1,代表可喚醒。

  • signalAll()的邏輯

這個(gè)方法和上面的方法一樣,不同點(diǎn)就是此方法是將條件隊(duì)列的節(jié)點(diǎn)一個(gè)一個(gè)全部移動(dòng)到等待隊(duì)列上去。

看的出來(lái)Condition中的條件隊(duì)列依賴等待隊(duì)列,具體使用可以參考ReentrantLock。你會(huì)發(fā)現(xiàn)在ReentrantLock鎖里面使用Condition,就相當(dāng)于在synchronized代碼塊中使用object類的wait方法和nottfyf。

為了更好的理解Condition,一起看下ArrayBlockingQueue的實(shí)現(xiàn),它是一個(gè)數(shù)組實(shí)現(xiàn)的先進(jìn)先出的有界阻塞隊(duì)列,隊(duì)列滿,入隊(duì)者等待,隊(duì)列空,出隊(duì)者等待。

這個(gè)隊(duì)列有兩個(gè)重要的特點(diǎn):先進(jìn)先出和隊(duì)列有界。

為保證先進(jìn)先出,需要加鎖處理,獲取到鎖的線程才有資格向隊(duì)列中放數(shù)據(jù)或者取出數(shù)據(jù)。

那如何保證隊(duì)列有界的情況下等待處理呢?這個(gè)時(shí)候就用到Condition了,它的邏輯是這樣的,所有想向隊(duì)列添加數(shù)據(jù)的和所有想從隊(duì)列取數(shù)據(jù)的線程一起競(jìng)爭(zhēng)鎖,拿到鎖的那個(gè)線程才有資格操作,ArrayBlockingQueue維護(hù)里兩個(gè)Condition對(duì)象,也就相當(dāng)于維護(hù)兩個(gè)條件隊(duì)列,如果是添加數(shù)據(jù)的某個(gè)線程搶到了鎖,在操作添加的時(shí)候,發(fā)現(xiàn)隊(duì)列已滿,此時(shí)該線程無(wú)法將數(shù)據(jù)插進(jìn)去,需要等待有一個(gè)數(shù)據(jù)被取走后才能做添加操作,但是該線程占有鎖資源,取數(shù)據(jù)的線程進(jìn)不來(lái),所以就無(wú)法進(jìn)行下去,ArrayBlockingQueue的做法是將該線程放入條件隊(duì)列阻塞掛起,等到有一個(gè)數(shù)據(jù)被取走后,再把條件隊(duì)列中的掛起的線程搬運(yùn)到鎖的等待隊(duì)里上去,從而再次獲取排隊(duì)搶鎖的資格。

之所以維護(hù)兩個(gè)Condition條件隊(duì)列是為了將添加數(shù)據(jù)的線程和取數(shù)據(jù)的線程分開(kāi),根據(jù)不同的條件操作不同的條件隊(duì)列。

有沒(méi)有發(fā)現(xiàn),這不就是synchronized代碼塊中的object類的wait方法嗎?

但是不同點(diǎn)是調(diào)用object類的wait方法阻塞的線程,要么只有一個(gè)被釋放,要么全部釋放。

而Condition就不同了,因?yàn)槟憧梢月暶鞫鄠€(gè)Condition對(duì)象,將不同條件下阻塞的線程放入不同的Condition對(duì)象,釋放的時(shí)候也按照條件釋放,這就真正意義上實(shí)現(xiàn)了按條件釋放。

我說(shuō)的釋放是重新獲取排隊(duì)搶奪資源的資格。

AQS中的中斷

不可中斷說(shuō)的是阻塞狀態(tài)不能被終止。

我們知道synchronized是不可中斷的鎖,當(dāng)線程因?yàn)楦?jìng)爭(zhēng)資源失敗而進(jìn)入阻塞狀態(tài)后,唯一能讓該線程結(jié)束阻塞的方式就是持有鎖資源的線程處理完成后,被阻塞的線程被喚醒。

synchronized中的阻塞狀態(tài)不可中斷是因?yàn)榫€程的阻塞喚醒是由操作系統(tǒng)來(lái)管理,而AQS中的阻塞之所以支持中斷是因?yàn)樯湘i是通過(guò)LockSupport類的park方法來(lái)實(shí)現(xiàn)的,當(dāng)線程調(diào)用park方法阻塞后,如果調(diào)用此線程interrupt方法,阻塞狀態(tài)就會(huì)中斷,也就是阻塞中的線程會(huì)被喚醒。

但是調(diào)用acquire上鎖的時(shí)候如果沒(méi)有獲取到鎖就會(huì)被阻塞,此時(shí)如果調(diào)用被阻塞線程的interrupt方法就會(huì)喚醒這個(gè)線程,但是此時(shí)被喚醒的線程處于循環(huán)之中,會(huì)重新去搶鎖,如果獲取不到依然會(huì)再次阻塞,也就是說(shuō)acquire方法中被阻塞的線程被中斷后只不過(guò)會(huì)讓線程提前加入搶鎖,但是并不會(huì)增加搶到鎖的概率,因?yàn)橹挥凶枞?duì)列的頭節(jié)點(diǎn)才有資格搶鎖。

這里介紹一個(gè)知識(shí)點(diǎn):常見(jiàn)的可中斷方法sleep,wait,park方法,這三個(gè)方法都會(huì)使得線程處于靜止?fàn)顟B(tài),此時(shí)調(diào)用interrupt方法,會(huì)中斷其靜止?fàn)顟B(tài),線程從而處于重新被激活的狀態(tài),不同的是被激活后的線程的中斷狀態(tài)是不一樣的,sleep和wait方法被激活后,線程的中斷狀態(tài)為false,而park方法被激活后,線程的中斷狀態(tài)為true,這是需要注意的。

按照上面的說(shuō)法AQS雖然支持中斷,但是似乎沒(méi)什么用,其實(shí)AQS還有一個(gè)相對(duì)于acquire方法不那么常用的方法tryAcquireNanos方法。

跟一下這個(gè)方法進(jìn)入doAcquireNanos方法,主要邏輯就在這個(gè)方法中,其實(shí)和tryAcquireNanos和acquire一樣,都是搶鎖,入隊(duì),阻塞,喚醒那一套邏輯。

不同的是tryAcquireNanos方法還具備兩個(gè)技能:

  1. 支持指定阻塞時(shí)間,一定時(shí)間后線程將會(huì)自動(dòng)喚醒,自動(dòng)喚醒后的線程的中斷狀態(tài)為false。
  2. 支持被中斷后拋出異常InterruptedException。
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

上面的代碼可以清楚的看到阻塞操作是通過(guò)這段代碼實(shí)現(xiàn):

LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));

parkNanos方法相對(duì)與park方法的區(qū)別就是parkNanos方法可以指定阻塞時(shí)間。

而下面這段代碼實(shí)現(xiàn)的就是阻塞被中斷的時(shí)候主動(dòng)拋出InterruptedException異常,可以讓方法外部捕獲到這個(gè)異常,從而達(dá)到真正的阻塞中斷。

if (Thread.interrupted())
                    throw new InterruptedException();


責(zé)任編輯:武曉燕 來(lái)源: 碼農(nóng)本農(nóng)
相關(guān)推薦

2020-08-06 10:45:30

JavaSpring面試題

2023-10-10 08:55:12

AQS阻塞

2018-06-28 15:40:04

AI 數(shù)據(jù)機(jī)器學(xué)習(xí)

2017-05-23 15:47:04

JavaScriptthis解析

2023-04-14 08:39:01

AQS方法JDK5

2020-10-12 18:00:39

JavaAQS代碼

2010-06-11 14:46:09

UML模型

2025-02-06 08:24:25

AQS開(kāi)發(fā)Java

2024-07-26 10:35:00

2017-03-29 09:08:25

Spring筆記

2024-09-04 09:43:36

2010-09-25 14:12:50

Java內(nèi)存分配

2017-11-14 19:19:07

人工智能自然語(yǔ)言處理百度

2022-08-30 17:41:53

中間件系統(tǒng)Java

2024-05-31 13:34:57

2013-05-27 14:06:14

Android開(kāi)發(fā)移動(dòng)開(kāi)發(fā)Intent機(jī)制

2015-12-16 10:30:18

前端開(kāi)發(fā)指南

2024-08-29 10:23:42

2024-11-11 17:20:52

2017-04-10 18:34:16

AndroidNotificatio
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

欧美日韩大陆在线| 国产午夜精品福利| 欧美日韩第一页| xxxx黄色片| 欧美特大特白屁股xxxx| 亚洲国产电影在线观看| 成人免费福利在线| 国产无套在线观看| 国产亚洲第一伦理第一区| 在线播放91灌醉迷j高跟美女| 欧美美女黄色网| 日韩a在线观看| 美女爽到高潮91| 久久久欧美精品| www亚洲色图| 北条麻妃在线一区二区免费播放| 色哟哟精品一区| 国产又粗又爽又黄的视频| 四虎在线视频| 麻豆精品精品国产自在97香蕉| 久久综合久久美利坚合众国| 熟女人妻在线视频| 国产日韩中文在线中文字幕| 精品久久久久久久中文字幕| 在线看无码的免费网站| 日韩精品123| 国产91富婆露脸刺激对白| 国产精品久久久久久搜索| 一级aaa毛片| 亚洲国产日韩欧美在线| 亚洲美女av黄| 久久久久久婷婷| 亚洲网站免费| 在线亚洲高清视频| 欧美变态另类刺激| 色网在线观看| 亚洲日穴在线视频| 亚洲春色在线视频| 头脑特工队2在线播放| 高清成人免费视频| 亚洲影院污污.| 亚洲视频在线观看免费视频| 久久九九电影| 91av视频在线| xxxxxx国产| 欧美日本二区| 久久最新资源网| av最新在线观看| 日本a级不卡| 国产一区二区三区毛片| 亚洲欧美视频在线播放| 国产精品jk白丝蜜臀av小说| 日韩欧美中文字幕一区| 日本77777| 激情久久99| 欧美日韩一区二区三区不卡| 日日噜噜噜噜久久久精品毛片| 综合日韩av| 日韩欧美极品在线观看| 欧美精品99久久| 麻豆国产在线| 国产黄在线免费观看| 亚洲激情中文| 九九精品在线观看| 精品一区在线观看视频| 欧美私人啪啪vps| 久久青草福利网站| 日韩高清精品免费观看| 国产一区二区三区久久久久久久久| 久久久女人电视剧免费播放下载| 日韩精品在线免费看| 夜夜嗨网站十八久久| 51ⅴ精品国产91久久久久久| 福利网址在线观看| 青青草国产精品亚洲专区无| 国产精品自拍小视频| 国产美女免费看| 懂色av中文字幕一区二区三区| 国产精品初高中精品久久| 日韩在线视频第一页| 97se亚洲国产综合自在线| 欧美凹凸一区二区三区视频| 成人欧美一区| 亚洲伦在线观看| 婷婷无套内射影院| 日韩在线影院| 这里只有精品99re| 五月开心播播网| 欧美综合一区| 欧美成人sm免费视频| 久久精品女人毛片国产| 美女91精品| 成人免费淫片aa视频免费| 精品免费久久久| 97精品电影院| 亚洲一区免费看| 97超碰在线免费| 在线看一区二区| 亚洲精品一二三四| 女人丝袜激情亚洲| 欧美成人在线免费视频| 精品不卡一区二区| 国产剧情一区二区| 久久艳妇乳肉豪妇荡乳av| 日韩在线免费电影| 亚洲成年人网站在线观看| 少妇性l交大片| 精品国产18久久久久久二百| 国产视频亚洲视频| 神马午夜精品91| 欧美亚洲自偷自偷| 91青青草免费观看| 国产免费av高清在线| 亚洲一区二区黄色| 欧美激情第3页| 欧美男人操女人视频| 久久人体大胆视频| 无码人妻丰满熟妇精品区| 国产酒店精品激情| 亚洲高清在线观看一区| 日韩理论视频| 欧美不卡视频一区| 国产亚洲精品久久久久久豆腐| 国产一区导航| 国产另类第一区| 黄色大片在线播放| 在线国产亚洲欧美| 免费成人蒂法网站| 狠狠干成人综合网| 91精品在线观| 137大胆人体在线观看| 欧美日韩亚洲一区二区三区| 日批免费观看视频| 婷婷成人基地| 国产精品永久免费| 九色在线免费| 日韩欧美亚洲综合| aaaaa一级片| 日韩视频二区| 精品国产_亚洲人成在线| 羞羞网站在线看| 91精品久久久久久久久99蜜臂| 免费在线观看a视频| 日韩精品成人一区二区三区| 久久影视中文粉嫩av| 波多野结衣中文字幕久久| 日韩欧美视频在线| 国精品无码一区二区三区| 久久66热re国产| 亚洲一区在线免费| 国产91欧美| 中文字幕无线精品亚洲乱码一区| 国产女主播喷水视频在线观看| 久久久亚洲精品石原莉奈| 国产精品免费成人| 欧美猛男男男激情videos| 日韩av片免费在线观看| 久久久久久女乱国产| 在线亚洲一区二区| 色www亚洲国产阿娇yao| 国产原创一区二区| 佐佐木明希av| 成人高潮a毛片免费观看网站| 国模私拍一区二区三区| 午夜影院免费视频| 色综合咪咪久久| 色哟哟精品观看| 日本不卡的三区四区五区| 午夜午夜精品一区二区三区文| 久久99国产精品二区高清软件| 色青青草原桃花久久综合| 97精品久久人人爽人人爽| 亚洲美女免费视频| 午夜剧场免费看| 麻豆成人在线| 一区二区三区四区欧美日韩| 欧美第一在线视频| 国内外成人免费激情在线视频网站| 午夜国产在线视频| 欧美性videosxxxxx| 在线观看亚洲网站| 成人av在线电影| caoporn超碰97| 亚洲情侣在线| 久99久在线| 99精品国自产在线| 欧美激情视频一区二区| 三级国产在线观看| 精品视频在线免费| 久久久久成人精品无码| 久久久久久亚洲综合影院红桃| 日韩在线第三页| 欧美婷婷在线| 日本免费高清不卡| 久久一级大片| 日韩美女免费观看| 在线观看电影av| 亚洲天堂影视av| av一区二区三| 欧美在线你懂得| 九九热这里有精品视频| 久久精品亚洲国产奇米99| 天天干天天曰天天操| 模特精品在线| 无码人妻精品一区二区蜜桃网站| 国产综合久久久| 懂色一区二区三区av片| 电影一区二区| 97视频免费在线观看| 蜜桃视频在线观看免费视频网站www| 亚洲国产精品大全| 91久久国语露脸精品国产高跟| 天天免费综合色| 欧美性x x x| 久久精品这里都是精品| 少妇被狂c下部羞羞漫画| 久草在线在线精品观看| 亚洲国产精品久久久久婷蜜芽| 91tv精品福利国产在线观看| 欧美日韩在线精品| 国产+成+人+亚洲欧洲在线| 国产欧美va欧美va香蕉在| 惠美惠精品网| 97久久超碰福利国产精品…| 18网站在线观看| 上原亚衣av一区二区三区| 色综合888| 日韩大片免费观看视频播放| 精品人妻伦一二三区久久| 欧美婷婷六月丁香综合色| 青青草免费观看视频| 一区二区欧美国产| 精品无码一区二区三区蜜臀| 亚洲国产成人午夜在线一区| 一区二区三区四区免费| 91亚洲国产成人精品一区二区三 | 川上优av一区二区线观看| 香蕉视频亚洲一级| 人九九综合九九宗合| 国产夫妻在线| 久久久这里只有精品视频| 婷婷av在线| 欧美男插女视频| 日本伦理一区二区| 色综合男人天堂| 男女羞羞视频在线观看| 欧美激情二区三区| 欧美家庭影院| 久久久久中文字幕| 欧美男人天堂| 国产99在线|中文| **在线精品| 国产精品久久久久久久久久久久久久| 都市激情亚洲一区| 国产精品久久久久久久久免费看 | 激情久久五月| 成人免费观看cn| 国产一区二区三区成人欧美日韩在线观看 | 欧美日韩电影在线观看| 人人超在线公开视频| 欧美第一黄色网| 免费在线看污片| 18一19gay欧美视频网站| 在线中文字幕播放| 国产精品扒开腿做| 亚洲精品伦理| 97影院在线午夜| 欧美黄色网视频| 欧美不卡三区| 成人激情诱惑| 免费成人深夜夜行网站视频| 欧美全黄视频| 国产美女三级视频| 久久精品免费观看| 久久无码专区国产精品s| 99热这里都是精品| 亚洲午夜久久久久久久国产| 《视频一区视频二区| 久久久精品人妻一区二区三区四| 婷婷综合另类小说色区| 91视频久久久| 69久久99精品久久久久婷婷| 隣の若妻さん波多野结衣| 日韩精品免费在线| 91精品国产91久久久久游泳池 | 999精品视频在线观看播放| 亚洲一区二区精品3399| 国产免费www| 日韩午夜在线影院| 黄色av网址在线免费观看| 欧美精品在线播放| 美女100%一区| 2022国产精品| 国际精品欧美精品| 欧美极品少妇无套实战| 天堂资源在线中文精品| 九色91porny| 久久久国产精品不卡| 日韩欧美国产成人精品免费| 欧美日韩一区二区在线| 国产精品欧美久久久久天天影视 | av大片在线看| 久久人人爽人人| 日本午夜精品久久久久| 久久av二区| 欧美激情麻豆| 亚洲天堂av线| 91在线观看下载| 欧美一区免费观看| 在线区一区二视频| 丰满人妻一区二区三区无码av | 精品人妻中文无码av在线 | 麻豆国产一区| 日韩资源av在线| 亚洲人成久久| 精品人妻一区二区三| 国产日产欧美一区二区三区| 日本在线视频免费| 日韩欧美一区二区三区在线| 国产高清在线观看| 欧美一二三视频| 成人台湾亚洲精品一区二区| 懂色av粉嫩av蜜臀av| 日本va欧美va瓶| 37p粉嫩大胆色噜噜噜| 亚洲成人第一页| 亚洲av无码国产综合专区| 日韩在线观看网站| 欧美xxxx做受欧美护士| 久久精品一区二区三区不卡免费视频| 欧美 日韩 国产精品免费观看| 一区二区三区视频网| 久久久久久免费网| 日韩不卡视频在线| 精品伊人久久97| 乱馆动漫1~6集在线观看| 国产亚洲二区| 在线日本成人| 免费黄色a级片| 亚洲一区国产视频| 亚洲高清在线观看视频| 久99九色视频在线观看| 久久中文字幕一区二区| gogogo免费高清日本写真| 久久se这里有精品| 国产三级精品三级观看| 欧美日韩国产美| 嫩草香蕉在线91一二三区| 国产综合香蕉五月婷在线| 国产精品久久久久久麻豆一区软件 | av在线麻豆| 亚洲综合中文字幕在线| 亚洲欧洲日韩| 久久久无码人妻精品无码| 一区二区三区在线播| 精品人妻无码一区二区三区蜜桃一| 美日韩丰满少妇在线观看| 另类视频一区二区三区| 国产一级不卡视频| 99视频在线观看一区三区| 久久国产精品免费看| 亚洲欧美日韩一区二区三区在线| 亚洲伦理影院| 亚洲一卡二卡三卡四卡无卡网站在线看| 老司机免费视频一区二区| 成人在线观看小视频| 日韩午夜电影av| 精精国产xxxx视频在线野外| 欧美一级爱爱| 国产在线日韩欧美| 久久亚洲成人av| 亚洲理论在线a中文字幕| 欧美a视频在线| 欧美 日韩 国产精品| k8久久久一区二区三区 | 电影午夜精品一区二区三区 | 亚洲人被黑人高潮完整版| www.av黄色| 欧日韩不卡在线视频| 日韩在线二区| 黑森林av导航| 色先锋久久av资源部| 日本综合在线| 国产精品一区二区免费看| 新67194成人永久网站| а天堂中文在线资源| 日韩精品中文字幕在线一区| 无遮挡在线观看| 免费在线观看污污视频| 99国产精品久| 97超碰资源站| 欧美一级bbbbb性bbbb喷潮片| 日韩在线观看电影完整版高清免费悬疑悬疑| 午夜激情影院在线观看| 欧美日韩激情美女| 久热国产在线| 你懂的网址一区二区三区| 国产综合久久久久久鬼色| 久久精品视频1| 欧美国产一区二区三区|