上節(jié)我們介紹了顯式鎖,本節(jié)介紹關(guān)聯(lián)的顯式條件,介紹其用法和原理。顯式條件也可以被稱做條件變量、條件隊(duì)列、或條件,后文我們可能會(huì)交替使用。

用法

基本概念和方法

鎖用于解決競(jìng)態(tài)條件問題,條件是線程間的協(xié)作機(jī)制。顯式鎖與synchronzied相對(duì)應(yīng),而顯式條件與wait/notify相對(duì)應(yīng)。wait/notify與synchronized配合使用,顯式條件與顯式鎖配合使用。

條件與鎖相關(guān)聯(lián),創(chuàng)建條件變量需要通過顯式鎖,Lock接口定義了創(chuàng)建方法:

Condition newCondition();

Condition表示條件變量,是一個(gè)接口,它的定義為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public interface Condition {  void await() throws InterruptedException;  void awaitUninterruptibly();  long awaitNanos(long nanosTimeout) throws InterruptedException;  boolean await(long time, TimeUnit unit) throws InterruptedException;  boolean awaitUntil(Date deadline) throws InterruptedException;  void signal();  void signalAll();
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

await()對(duì)應(yīng)于Object的wait(),signal()對(duì)應(yīng)于notify,signalAll()對(duì)應(yīng)于notifyAll(),語義也是一樣的。

與Object的wait方法類似,await也有幾個(gè)限定等待時(shí)間的方法,但功能更多一些:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

//等待時(shí)間是相對(duì)時(shí)間,如果由于等待超時(shí)返回,返回值為false,否則為trueboolean await(long time, TimeUnit unit) throws InterruptedException;//等待時(shí)間也是相對(duì)時(shí)間,但參數(shù)單位是納秒,返回值是nanosTimeout減去實(shí)際等待的時(shí)間long awaitNanos(long nanosTimeout) throws InterruptedException;//等待時(shí)間是絕對(duì)時(shí)間,如果由于等待超時(shí)返回,返回值為false,否則為trueboolean awaitUntil(Date deadline) throws InterruptedException;

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

這些await方法都是響應(yīng)中斷的,如果發(fā)生了中斷,會(huì)拋出InterruptedException,但中斷標(biāo)志位會(huì)被清空。Condition還定義了一個(gè)不響應(yīng)中斷的等待方法:

void awaitUninterruptibly();

該方法不會(huì)由于中斷結(jié)束,但當(dāng)它返回時(shí),如果等待過程中發(fā)生了中斷,中斷標(biāo)志位會(huì)被設(shè)置。

一般而言,與Object的wait方法一樣,調(diào)用await方法前需要先獲取鎖,如果沒有鎖,會(huì)拋出異常IllegalMonitorStateException。await在進(jìn)入等待隊(duì)列后,會(huì)釋放鎖,釋放CPU,當(dāng)其他線程將它喚醒后,或等待超時(shí)后,或發(fā)生中斷異常后,它都需要重新獲取鎖,獲取鎖后,才會(huì)從await方法中退出。

另外,與Object的wait方法一樣,await返回后,不代表其等待的條件就一定滿足了,通常要將await的調(diào)用放到一個(gè)循環(huán)內(nèi),只有條件滿足后才退出。

一般而言,signal/signalAll與notify/notifyAll一樣,調(diào)用它們需要先獲取鎖,如果沒有鎖,會(huì)拋出異常IllegalMonitorStateException。signal與notify一樣,挑選一個(gè)線程進(jìn)行喚醒,signalAll與notifyAll一樣,喚醒所有等待的線程,但這些線程被喚醒后都需要重新競(jìng)爭(zhēng)鎖,獲取鎖后才會(huì)從await調(diào)用中返回。

用法示例

ReentrantLock實(shí)現(xiàn)了newCondition方法,通過它,我們來看下條件的基本用法。我們實(shí)現(xiàn)與67節(jié)類似的例子WaitThread,一個(gè)線程啟動(dòng)后,在執(zhí)行一項(xiàng)操作前,等待主線程給它指令,收到指令后才執(zhí)行,示例代碼為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public class WaitThread extends Thread {    private volatile boolean fire = false;    private Lock lock = new ReentrantLock();    private Condition condition = lock.newCondition();

    @Override    public void run() {        try {
            lock.lock();            try {                while (!fire) {
                    condition.await();
                }
            } finally {
                lock.unlock();
            }
            System.out.println("fired");
        } catch (InterruptedException e) {
            Thread.interrupted();
        }
    }    public void fire() {
        lock.lock();        try {            this.fire = true;
            condition.signal();
        } finally {
            lock.unlock();
        }
    }    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread();
        waitThread.start();
        Thread.sleep(1000);
        System.out.println("fire");
        waitThread.fire();
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

需要特別注意的是,不要將signal/signalAll與notify/notifyAll混淆,notify/notifyAll是Object中定義的方法,Condition對(duì)象也有,稍不注意就會(huì)誤用,比如,對(duì)上面例子中的fire方法,可能會(huì)寫為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public void fire() {
    lock.lock();    try {        this.fire = true;
        condition.notify();
    } finally {
        lock.unlock();
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

寫成這樣,編譯器不會(huì)報(bào)錯(cuò),但運(yùn)行時(shí)會(huì)拋出IllegalMonitorStateException,因?yàn)閚otify的調(diào)用不在synchronized語句內(nèi)。

同樣,避免將鎖與synchronzied混用,那樣非常令人混淆,比如:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public void fire() {    synchronized(lock){        this.fire = true;
        condition.signal();
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

記住,顯式條件與顯式鎖配合,wait/notify與synchronized配合。

生產(chǎn)者/消費(fèi)者模式

67節(jié),我們用wait/notify實(shí)現(xiàn)了生產(chǎn)者/消費(fèi)者模式,我們提到了wait/notify的一個(gè)局限,它只能有一個(gè)條件等待隊(duì)列,分析等待條件也很復(fù)雜。在生產(chǎn)者/消費(fèi)者模式中,其實(shí)有兩個(gè)條件,一個(gè)與隊(duì)列滿有關(guān),一個(gè)與隊(duì)列空有關(guān)。使用顯式鎖,可以創(chuàng)建多個(gè)條件等待隊(duì)列。下面,我們用顯式鎖/條件重新實(shí)現(xiàn)下其中的阻塞隊(duì)列,代碼為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

static class MyBlockingQueue<E> {    private Queue<E> queue = null;    private int limit;    private Lock lock = new ReentrantLock();    private Condition notFull  = lock.newCondition();    private Condition notEmpty = lock.newCondition();    public MyBlockingQueue(int limit) {        this.limit = limit;
        queue = new ArrayDeque<>(limit);
    }    public void put(E e) throws InterruptedException {
        lock.lockInterruptibly();        try{            while (queue.size() == limit) {
                notFull.await();
            }
            queue.add(e);
            notEmpty.signal();    
        }finally{
            lock.unlock();
        }
    }    public E take() throws InterruptedException {
        lock.lockInterruptibly();        try{            while (queue.isEmpty()) {
                notEmpty.await();
            }
            E e = queue.poll();
            notFull.signal();            return e;    
        }finally{
            lock.unlock();
        }
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

定義了兩個(gè)等待條件:不滿(notFull)、不空(notEmpty),在put方法中,如果隊(duì)列滿,則在noFull上等待,在take方法中,如果隊(duì)列空,則在notEmpty上等待,put操作后通知notEmpty,take操作后通知notFull。

這樣,代碼更為清晰易讀,同時(shí)避免了不必要的喚醒和檢查,提高了效率。Java并發(fā)包中的類ArrayBlockingQueue就采用了類似的方式實(shí)現(xiàn)。

實(shí)現(xiàn)原理
ConditionObject
理解了顯式條件的概念和用法,我們來看下ReentrantLock是如何實(shí)現(xiàn)它的,其newCondition()的代碼為:

public Condition newCondition() {    return sync.newCondition();
}

sync是ReentrantLock的內(nèi)部類對(duì)象,其newCondition()代碼為:

final ConditionObject newCondition() {    return new ConditionObject();
}

ConditionObject是AQS中定義的一個(gè)內(nèi)部類,不了解AQS請(qǐng)參看上節(jié)。ConditionObject的實(shí)現(xiàn)也比較復(fù)雜,我們通過一些主要代碼來簡(jiǎn)要探討其實(shí)現(xiàn)原理。ConditionObject內(nèi)部也有一個(gè)隊(duì)列,表示條件等待隊(duì)列,其成員聲明為:

//條件隊(duì)列的頭節(jié)點(diǎn)private transient Node firstWaiter;//條件隊(duì)列的尾節(jié)點(diǎn)private transient Node lastWaiter;

ConditionObject是AQS的成員內(nèi)部類,它可以直接訪問AQS中的數(shù)據(jù),比如AQS中定義的鎖等待隊(duì)列。

我們看下幾個(gè)方法的實(shí)現(xiàn),先看await方法。

await實(shí)現(xiàn)分析

下面是await方法的代碼,我們通過添加注釋解釋其基本思路。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public final void await() throws InterruptedException {    // 如果等待前中斷標(biāo)志位已被設(shè)置,直接拋異常
    if (Thread.interrupted())        throw new InterruptedException();    // 1.為當(dāng)前線程創(chuàng)建節(jié)點(diǎn),加入條件等待隊(duì)列
    Node node = addConditionWaiter();    // 2.釋放持有的鎖
    int savedState = fullyRelease(node);    int interruptMode = 0;    // 3.放棄CPU,進(jìn)行等待,直到被中斷或isOnSyncQueue變?yōu)閠rue    // isOnSyncQueue為true表示節(jié)點(diǎn)被其他線程從條件等待隊(duì)列    // 移到了外部的鎖等待隊(duì)列,等待的條件已滿足
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)            break;
    }    // 4.重新獲取鎖
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;    if (node.nextWaiter != null) // clean up if cancelled        unlinkCancelledWaiters();    // 5.處理中斷,拋出異?;蛟O(shè)置中斷標(biāo)志位
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

awaitNanos實(shí)現(xiàn)分析

awaitNanos與await的實(shí)現(xiàn)是基本類似的,區(qū)別主要是會(huì)限定等待的時(shí)間,如下所示:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public final long awaitNanos(long nanosTimeout) throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();
    Node node = addConditionWaiter();    int savedState = fullyRelease(node);    long lastTime = System.nanoTime();    int interruptMode = 0;    while (!isOnSyncQueue(node)) {        if (nanosTimeout <= 0L) {            //等待超時(shí),將節(jié)點(diǎn)從條件等待隊(duì)列移到外部的鎖等待隊(duì)列            transferAfterCancelledWait(node);            break;
        }        //限定等待的最長(zhǎng)時(shí)間
        LockSupport.parkNanos(this, nanosTimeout);        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)            break;        long now = System.nanoTime();        //計(jì)算下次等待的最長(zhǎng)時(shí)間
        nanosTimeout -= now - lastTime;
        lastTime = now;
    }    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;    if (node.nextWaiter != null)
        unlinkCancelledWaiters();    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);    return nanosTimeout - (System.nanoTime() - lastTime);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

signal實(shí)現(xiàn)分析

signal方法代碼為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public final void signal() {    //驗(yàn)證當(dāng)前線程持有鎖
    if (!isHeldExclusively())        throw new IllegalMonitorStateException();    //調(diào)用doSignal喚醒等待隊(duì)列中第一個(gè)線程
    Node first = firstWaiter;    if (first != null)
        doSignal(first);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

doSignal的代碼就不列舉了,其基本邏輯是:

  1. 將節(jié)點(diǎn)從條件等待隊(duì)列移到鎖等待隊(duì)列

  2. 調(diào)用LockSupport.unpark將線程喚醒

小結(jié)

本節(jié)介紹了顯式條件的用法和實(shí)現(xiàn)原理。它與顯式鎖配合使用,與wait/notify相比,可以支持多個(gè)條件隊(duì)列,代碼更為易讀,效率更高,使用時(shí)注意不要將signal/signalAll誤寫為notify/notifyAll。

70節(jié)到本節(jié),我們介紹了Java并發(fā)包的基礎(chǔ) - 原子變量和CAS、顯式鎖和條件,基于這些,Java并發(fā)包還提供了很多更為易用的高層數(shù)據(jù)結(jié)構(gòu)、工具和服務(wù),從下一節(jié)開始,我們先探討一些并發(fā)數(shù)據(jù)結(jié)構(gòu)。

(與其他章節(jié)一樣,本節(jié)所有代碼位于 https://github.com/swiftma/program-logic)

----------------

未完待續(xù),查看最新文章,敬請(qǐng)關(guān)注微信公眾號(hào)“老馬說編程”(掃描下方二維碼),從入門到高級(jí),深入淺出,老馬和你一起探索Java編程及計(jì)算機(jī)技術(shù)的本質(zhì)。用心原創(chuàng),保留所有版權(quán)。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

http://www.cnblogs.com/swiftma/p/6528219.html