java編程思想-多線程總結(jié)(三)

三、終結(jié)任務(wù)

1. 在阻塞時(shí)終結(jié)

線程狀態(tài)

一個(gè)線程可以處于以下四種狀態(tài)之一:

  • 1)新建(new):當(dāng)線程被創(chuàng)建時(shí),他只會(huì)短暫的處于這種狀態(tài)。此時(shí),他已經(jīng)分配了必須的系統(tǒng)資源,并執(zhí)行了初始化。此刻線程已經(jīng)有資格獲得CPU時(shí)間了,之后調(diào)度器將把這個(gè)線程轉(zhuǎn)變?yōu)榭蛇\(yùn)行zhuang't狀態(tài)或阻塞狀態(tài)。
  • 2)就緒(Runnable):在這種狀態(tài)下,只要調(diào)度器把時(shí)間片分配給線程,線程就可以運(yùn)行。也就是說(shuō),在任意時(shí)刻,線程可以運(yùn)行也可以不運(yùn)行。只要調(diào)度器能分配時(shí)間片給線程,他就可以運(yùn)行,這不同于死亡和阻塞狀態(tài)。
  • 3)阻塞(Blocked):線程能夠運(yùn)行,但是某個(gè)條件阻止他的運(yùn)行。當(dāng)線程處于阻塞狀態(tài)時(shí),調(diào)度器將忽略線程,不會(huì)分配給線程任何CPU時(shí)間。直到線程重新進(jìn)入就緒狀態(tài),他才可能執(zhí)行操作。
  • 4)死亡(Dead):處于死亡或終止?fàn)顟B(tài)的線程將是不可調(diào)度的,并且再也不會(huì)得到CPU時(shí)間,它的任務(wù)已結(jié)束,或不再是可運(yùn)行的。任務(wù)死亡的通常方式是從run()方法返回,但是任務(wù)的線程還可以被中斷。

進(jìn)入阻塞狀態(tài)

一個(gè)任務(wù)進(jìn)入阻塞狀態(tài),可能有如下原因:

  • 1)通過(guò)調(diào)用sleep()使任務(wù)進(jìn)入休眠狀態(tài),在這種情況下,任務(wù)在指定的時(shí)間內(nèi)不會(huì)運(yùn)行。
  • 2)調(diào)用wait()使線程掛起,直到線程得到了notify()或notifyAll()消息(或者concurrent類庫(kù)中等價(jià)的signal()或signalAll()消息),線程才會(huì)進(jìn)入就緒狀態(tài)。
  • 3)任務(wù)在等待某個(gè)輸入/輸出完成。
  • 4)任務(wù)試圖在某個(gè)對(duì)象上調(diào)用其同步控制方法,但是對(duì)象鎖不可用,因?yàn)榱硪粋€(gè)任務(wù)已經(jīng)獲取了這個(gè)鎖。

四、線程之間的協(xié)作

當(dāng)使用線程來(lái)同時(shí)運(yùn)行多個(gè)任務(wù)時(shí),可以通過(guò)使用鎖(互斥)來(lái)同步兩個(gè)任務(wù)的行為,從而使得一個(gè)任務(wù)不會(huì)干涉另一個(gè)任務(wù)的資源。也就是說(shuō),如果兩個(gè)任務(wù)在交替著步入某向資源(通常是內(nèi)存),你可以使用互斥來(lái)使得任何時(shí)刻只有一個(gè)任務(wù)可以訪問(wèn)這項(xiàng)資源。

當(dāng)任務(wù)協(xié)作時(shí),關(guān)鍵問(wèn)題是這些任務(wù)之間的握手。為了實(shí)現(xiàn)這種握手,我們使用了相同的基礎(chǔ)特性:互斥。在這種情況下,互斥能夠確保只有一個(gè)任務(wù)可以響應(yīng)某個(gè)信號(hào),這樣就可以根除任何可能的競(jìng)爭(zhēng)條件。在互斥之上,我們?yōu)槿蝿?wù)添加了一種途徑,可以將其自身掛起,直到某些外部條件發(fā)生變化,表示是時(shí)候讓這個(gè)任務(wù)向前開(kāi)動(dòng)了為止。

1. wait()和notifyAll()

wait()使你可以等待某個(gè)條件發(fā)生變化,而改變這個(gè)條件超出了當(dāng)前方法的控制能力。通常,這種條件將由另一個(gè)任務(wù)來(lái)改變。你肯定不想在你的任務(wù)測(cè)試這個(gè)條件的同時(shí),不斷地進(jìn)行空循環(huán),這被稱為忙等待,通暢是一種不良的CPU周期使用方式。因此wait()會(huì)在等待外部世界產(chǎn)生變化的時(shí)候?qū)⑷蝿?wù)掛起,并且只有在notify()或notifyAll()發(fā)生時(shí),即表示發(fā)生了某些感興趣的事物,這個(gè)任務(wù)才會(huì)被喚醒并去檢查所產(chǎn)生的變化。因此,wait()提供了一種在任務(wù)之間對(duì)活動(dòng)同步的形式。

調(diào)用sleep()的時(shí)候鎖并沒(méi)有被釋放,調(diào)用yield()也屬于這種情況,理解這一點(diǎn)很重要。另一方面,當(dāng)一個(gè)任務(wù)在方法里遇到了對(duì)wait()的調(diào)用的時(shí)候,線程的執(zhí)行被掛起,對(duì)象上的鎖被釋放。因?yàn)閣ait()將釋放鎖,這就意味著另一個(gè)任務(wù)可以獲得這個(gè)鎖,因此在該對(duì)象(現(xiàn)在是未鎖定的)中的其他synchronized方法可以在wait()期間被調(diào)用。這點(diǎn)至關(guān)重要。因?yàn)檫@些其他的方法通常將會(huì)產(chǎn)生改變,而這種改變正是使被掛起的任務(wù)重新喚醒所感興趣的變化。

有兩種形式的wait()。第一種版本接受毫秒數(shù)作為參數(shù),含義與sleep()方法里的參數(shù)意思相同,都是指“在此期間暫停”。但是與sleep()不同的是,對(duì)于wait()而言:

  • 1)在wait()期間對(duì)象鎖是釋放的。
  • 2)可以通過(guò)notify()、notifyAll(),或者令時(shí)間到期,從wait()中恢復(fù)執(zhí)行。

第二種,也是更常用形式的wait()不接受任何參數(shù)。這種wait()將無(wú)限等待下去,直到線程接收到notify()或者notifyAll()消息。

wait()、notify()以及notifyAll()有一個(gè)比較特殊的方面,那就是這些方法是基類Object的一部分,而不屬于Threadde的一部分。所以你可以把wait()放進(jìn)任何同步控制方法里,而不用考慮這個(gè)類是繼承自Thread還是實(shí)現(xiàn)了Runnable接口。實(shí)際上,只能在同步控制方法或同步控制塊li里調(diào)用wait()、notify()和notifyAll()(因?yàn)椴挥貌僮麈i,所以sleep()可以在非同步控制方法里調(diào)用)。調(diào)用wait()、notify()和notifyAll()的任務(wù)在調(diào)用這些方法前必須擁有對(duì)象的鎖。

可以讓另一個(gè)對(duì)象執(zhí)行某種操作以維護(hù)其自己的鎖。要這么做的話,必須首先得到對(duì)象的鎖。比如,如果要向?qū)ο髕發(fā)送notifyAll(),就必須在能夠取得x的鎖的同步控制塊中這么做:

synchronized(x){
    x.notifyAll();
}

我們看一下示例,WaxOMatic.java有兩個(gè)過(guò)程,一個(gè)是將la蠟涂到Car上,一個(gè)是拋光它。拋光任務(wù)在涂蠟任務(wù)完成之前,是不能執(zhí)行其工作的,而涂蠟任務(wù)在涂另一層蠟之前,必須等待拋光任務(wù)完成。WaxOn和WaxOff都使用了Car對(duì)象,該對(duì)象在這些任務(wù)等待條件變化的時(shí)候,使用wait()和notifyAll()來(lái)掛起和重新啟動(dòng)這些任務(wù):

public class WaxOMatic {
    public static void main(String[] args) throws Exception{
        Car car = new Car();
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new WaxOff(car));
        exec.execute(new WaxOn(car));
        TimeUnit.SECONDS.sleep(5);
        exec.shutdown();
    }
}

class Car {
    private boolean waxOn = false;

    public synchronized void waxed() {
        waxOn = true;
        notifyAll();
    }

    public synchronized void buffed() {
        waxOn = false;
        notifyAll();
    }

    public synchronized void waitForWaxing() throws InterruptedException {
        while (waxOn == false) {
            wait();
        }
    }

    public synchronized void waitForBuffing() throws InterruptedException {
        while (waxOn == true) {
            wait();
        }
    }

}

class WaxOn implements Runnable {
    private Car car;

    public WaxOn(Car car) {
        this.car = car;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println("wax on");
                TimeUnit.MILLISECONDS.sleep(200);
                car.waxed();
                car.waitForBuffing();
            }
        }catch (InterruptedException e){
            System.out.println("exiting via interrupt");
        }
        System.out.println("ending wax on task");
    }
}

class WaxOff implements Runnable {
    private Car car;

    public WaxOff(Car car) {
        this.car = car;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                car.waitForWaxing();
                System.out.println("wax off");
                TimeUnit.MILLISECONDS.sleep(200);
                car.buffed();
            }
        }catch (InterruptedException e){
            System.out.println("exiting via interrupt");
        }
        System.out.println("ending wax off task");
    }
}

錯(cuò)失的信號(hào)

當(dāng)兩個(gè)線程使用notify()/wait()或notifyAll()/wait()進(jìn)行協(xié)作時(shí),有可能會(huì)錯(cuò)過(guò)某個(gè)信號(hào),假設(shè)T1是通知T2的線程,而這兩個(gè)線程都是通過(guò)下面(有缺陷的)方式實(shí)現(xiàn)的:

T1:
synchronized(sharedMonitor){
    <setup condition for T2>
    sharedMonitor.notify();
}

T2:
while(someCondition){
    //Point 1
    synchronized(sharedMonitor){
        sharedMonitor.wait();
    }
}

<setup condition for T2>是防止T2調(diào)用wait()的一個(gè)動(dòng)作,當(dāng)然前提是T2還沒(méi)有調(diào)用wait()。

假設(shè)T2對(duì)someCondition求值并發(fā)現(xiàn)其為true。在Point1,線程調(diào)度器可能切換到了T1。而T1將執(zhí)行其設(shè)置,然后調(diào)用notify()。當(dāng)T2得以繼續(xù)執(zhí)行時(shí),此時(shí)對(duì)于T2來(lái)說(shuō),時(shí)機(jī)已經(jīng)晚了,以至于不能意識(shí)到這個(gè)條件已經(jīng)發(fā)生了變化,因此會(huì)盲目進(jìn)入wait()。此時(shí)notify()將錯(cuò)失,而T2也將無(wú)限的等待這個(gè)已經(jīng)發(fā)送過(guò)的信號(hào),從而發(fā)生死鎖。

該問(wèn)題的解決方案是防止在someCondition變量上產(chǎn)生競(jìng)爭(zhēng)條件。下面是T2正確的執(zhí)行方式:

synchronized(sharedMonitor){
    while(someCondition){
        sharedMonitor.wait();
    }
}

現(xiàn)在,如果T1首先執(zhí)行,當(dāng)控制返回T2時(shí),它將發(fā)現(xiàn)條件發(fā)生了變化,從而不會(huì)進(jìn)入wait()。反過(guò)來(lái),如果T2首先執(zhí)行,那它將進(jìn)入wait(),并且稍后會(huì)由T1喚醒。因此,信號(hào)不會(huì)錯(cuò)失。

2. 生產(chǎn)者-消費(fèi)者與隊(duì)列

wait()和notifyAll()方法是一種非常低級(jí)的方式解決了任何互操作問(wèn)題,即每次交互shi時(shí)都握手。在許多情況下,你可以瞄向更高的抽象級(jí)別,使用同步隊(duì)列來(lái)解決任務(wù)協(xié)作問(wèn)題,同步隊(duì)列在任何時(shí)刻都只允許一個(gè)任務(wù)插入或移除元素。在java.util.concurrent.BlockingQueue接口中提供了這個(gè)隊(duì)列,這個(gè)接口有大量的標(biāo)準(zhǔn)實(shí)現(xiàn)。你通??梢允褂肔inkedBlockingQueue,他是一個(gè)無(wú)界隊(duì)列,還可以使用ArrayBlockingQueue,它具有固定的尺寸,因此你可以在他被阻塞之前,向其中放置有限數(shù)量的元素。

如果消費(fèi)者任務(wù)試圖從隊(duì)列中獲取對(duì)象,而該隊(duì)列此時(shí)為空,那么這些隊(duì)列還可以掛起消費(fèi)者任務(wù),并且當(dāng)有更多的元素可用時(shí)恢復(fù)消費(fèi)者任務(wù)。阻塞隊(duì)列可以解決非常大量的問(wèn)題,而其方式與wait()和notifyAll()相比,則簡(jiǎn)單可靠的多。

3. 死鎖

任務(wù)可以變成阻塞狀態(tài),所以就可能出現(xiàn)這種情況:某個(gè)任務(wù)在等待另一個(gè)任務(wù),而后者又等待別的任務(wù),這樣一直下去,直到這條鏈條上的任務(wù)又在等待第一個(gè)任務(wù)釋放鎖。這得到了一個(gè)任務(wù)之間等待的連續(xù)循環(huán),沒(méi)有哪個(gè)線程能繼續(xù),這被稱為死鎖。

要修正死鎖問(wèn)題,你必須明白,當(dāng)以下四個(gè)條件同時(shí)滿足時(shí),就會(huì)發(fā)生死鎖:

  • 1)互斥條件。任務(wù)使用的資源中至少有一個(gè)是不能共享的。
  • 2)至少有一個(gè)任務(wù)它必須持有一個(gè)資源且正在等待獲取一個(gè)當(dāng)前被別的任務(wù)持有的資源。
  • 3)資源不能被任務(wù)搶占,任務(wù)必須把資源釋放當(dāng)做普通事件。
  • 4)必須有循環(huán)等待,這時(shí)一個(gè)任務(wù)等待其他任務(wù)所持有的資源,后者又在等待另一個(gè)任務(wù)所持有的資源,這樣一直等待下去,直到有一個(gè)任務(wù)在等待第一個(gè)任務(wù)所持有的資源,使得大家都被鎖住。

因?yàn)橐l(fā)生死鎖的話,所有這些條件必須全部滿足;所以要防止死鎖的話,只需破壞其中一個(gè)即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容