JAVA技巧:修正Java中wait方法超時語意模糊

字號:

if (timeSoFar >= msecTimeout)
    throw new TimeoutException ();
    else
    waitTime = timeout - timeSoFar;
    }
    else
    break;
    }
    }
    }
    public final void announce() {
    object_.notifyAll ();
    }
    }
    使用方法介紹
    本小節(jié)我們將對上一節(jié)給出的抽象基類WaitWithTiming的使用方法進(jìn)行詳細(xì)的介紹。我們當(dāng)然可以直接使得ActiveQueue繼承自WaitWithTiming,并實(shí)現(xiàn)相應(yīng)的抽象hook方法condition,但是這樣做有一個弊端,就是對于ActiveQueue我們只能夠?qū)崿F(xiàn)僅僅一個condition,如果我們要添加針對dequeue時隊(duì)列為空的條件判斷邏輯就無能為力了,因?yàn)閃aitWithWaiting僅僅只有一個condition方法(其實(shí),即使有多個也沒有辦法做到通用,因?yàn)椴荒軐唧w的應(yīng)用的需求進(jìn)行假設(shè))。
    我們推薦的使用方法是,根據(jù)具體應(yīng)用的需求,整理出需要的判斷條件,創(chuàng)建相應(yīng)的類來表示這些判斷條件,使這些用來表示具體判斷條件的類繼承自WaitWithTiming,這些類中具體的條件判斷邏輯的實(shí)現(xiàn)可以使用相應(yīng)的具體的應(yīng)用實(shí)體。比如:對于本文開始所列舉的應(yīng)用,我們需要的判斷條件為隊(duì)列為滿,所以我們可以定義一個QueueFullCondition類繼承自WaitWithTiming,在QueueFullCondition中實(shí)現(xiàn)抽象的hook方法condition的邏輯,在該邏輯中在使用ActiveQueue的isFull方法。使用這種委托的方法,我們就可以比較有效的解決一個對象同時需要多個判斷條件的問題(不同的判斷條件只需定義不同的子類即可)。相應(yīng)的UML結(jié)構(gòu)圖和關(guān)鍵代碼實(shí)現(xiàn)如下:
    關(guān)鍵代碼片斷:
    class QueueFullCondition extends WaitWithTiming
    {
    public QueueFullCondition (ActiveQueue aq)
    { super (aq); } // 為WaitWithTiming中的object_賦值
    public boolean condition () {
    ActiveQueue aq = (ActiveQueue) object_; //使用ActiveQueue來實(shí)現(xiàn)具體的判斷邏輯
    return aq.isFull ();
    }
    }
    class ActiveQueue {
    ...
    public synchronized void enqueue(ClientRequest cr, long timeout)
    throws InterruptedException, TimeoutException
    {
    //具有時限控制的等待
    queueFullCondition_.timedWait (timeout);
    // 把用戶請求添加進(jìn)隊(duì)列
    //喚醒等待在ActiveQueue上的線程
    queueFullCondition_.announce ();
    }
    ...
    private QueueFullCondition queueFullCondition_ = new QueueFullCondition (this);
    }
    要注意的問題
    如果讀者朋友仔細(xì)觀察的話,就會覺察到在WaitWithTiming類中的timedWait方法的定義中沒有添加synchronized關(guān)鍵字,這一點(diǎn)是非常關(guān)鍵的,因?yàn)槭菫榱吮苊庠诰帉懖l(fā)的Java應(yīng)用時一個常見的死鎖問題:嵌套的monitor。下面對于這個問題進(jìn)行簡單的介紹,關(guān)于這一問題更為詳細(xì)的論述請參見參考文獻(xiàn)〔1〕。
    什么是嵌套的monitor問題呢?嵌套的monitor是指:當(dāng)一個線程獲得了對象A的monitor鎖,接著又獲得了對象B的monitor鎖,在還沒有釋放對象B的monitor鎖時,調(diào)用了對象B的wait方法,此時,該線程釋放對象B的monitor鎖并等待在對象B的線程等待隊(duì)列上,但是此時該線程還擁有對象A的monitor鎖。如果該線程的喚起條件依賴于另一個線程首先要獲得對象A的monitor鎖的話,就會引起死鎖,因?yàn)榇藭r別的線程無法獲得上述線程還沒有釋放的對象A的monitor鎖,結(jié)果就出現(xiàn)了死鎖情況。一般的解決方案是:在設(shè)計(jì)時線程不要獲取對象B的monitor鎖,而僅僅使用對象A的monitor鎖。
    針對我們前面列舉的例子,ActiveQueu可以類比為對象A,queueFullContion_可以類比為對象B,如果我們在timedWait方法前面添加上synchronized關(guān)鍵字,就有可能會發(fā)生上述的死鎖情況,因?yàn)楫?dāng)我們在調(diào)用ActiveQueu的enqueue方法中調(diào)用了queueFullContion_的timedWait方法后,如果隊(duì)列為滿,雖然我們釋放了queueFullContion_的monitor鎖,但是我們還持有ActiveQueue的monitor鎖,并且我們的喚醒條件依賴于另外一個線程調(diào)用ActivcQueue的dequeue方法,但是因?yàn)榇藭r我們沒有釋放ActiveQueue的monitor鎖,所以另外的線程就無法調(diào)用ActiveQueu的dequeue方法,那么結(jié)果就是這兩個線程就都只能夠等待。
    總結(jié)
    本文對于Java中wait方法超時語意的模糊性進(jìn)行了分析,并給出了一個比較通用的解決方案,本解決方案對于需要精確的超時語意的應(yīng)用還是無法很好的適用的,因?yàn)榉桨钢兴o出的關(guān)于超時計(jì)算的算法是不精確的。還有一點(diǎn)就是有關(guān)嵌套monitor的問題,在編寫多線程的Java程序時一定要特別注意,否則非常容易引起死鎖。其實(shí),本文所講述的所有問題的根源都是由于Java對于wait方法超時語意實(shí)現(xiàn)的模糊性造成的,如果在后續(xù)的Java版本中對此進(jìn)行了修正,那么本文給出的解決方案就是多余的了