鍍金池/ 教程/ Java/ 線程掛起、恢復(fù)與終止
并發(fā)新特性—信號(hào)量 Semaphore
線程間協(xié)作:wait、notify、notifyAll
notify 通知的遺漏
notifyAll 造成的早期通知問(wèn)題
多線程的實(shí)現(xiàn)方法
深入 Java 內(nèi)存模型(1)
多線程環(huán)境下安全使用集合 API
并發(fā)新特性—Lock 鎖與條件變量
生產(chǎn)者—消費(fèi)者模型
深入 Java 內(nèi)存模型(2)
線程中斷
Volatile 關(guān)鍵字(上)
并發(fā)新特性—阻塞隊(duì)列與阻塞棧
可重入內(nèi)置鎖
守護(hù)線程與線程阻塞
并發(fā)新特性—障礙器 CyclicBarrier
Volatile 關(guān)鍵字(下)
synchronized 關(guān)鍵字
synchronized 的另個(gè)一重要作用:內(nèi)存可見(jiàn)性
并發(fā)新特性—Executor 框架與線程池
并發(fā)性與多線程介紹
死鎖
實(shí)現(xiàn)內(nèi)存可見(jiàn)性的兩種方法比較:synchronized 和 Volatile
線程掛起、恢復(fù)與終止

線程掛起、恢復(fù)與終止

掛起和恢復(fù)線程

Thread 的 API 中包含兩個(gè)被淘汰的方法,它們用于臨時(shí)掛起和重啟某個(gè)線程,這些方法已經(jīng)被淘汰,因?yàn)樗鼈兪遣话踩?,不穩(wěn)定的。如果在不合適的時(shí)候掛起線程(比如,鎖定共享資源時(shí)),此時(shí)便可能會(huì)發(fā)生死鎖條件——其他線程在等待該線程釋放鎖,但該線程卻被掛起了,便會(huì)發(fā)生死鎖。另外,在長(zhǎng)時(shí)間計(jì)算期間掛起線程也可能導(dǎo)致問(wèn)題。

下面的代碼演示了通過(guò)休眠來(lái)延緩運(yùn)行,模擬長(zhǎng)時(shí)間運(yùn)行的情況,使線程更可能在不適當(dāng)?shù)臅r(shí)候被掛起:

public class DeprecatedSuspendResume extends Object implements Runnable{  

    //volatile關(guān)鍵字,表示該變量可能在被一個(gè)線程使用的同時(shí),被另一個(gè)線程修改  
    private volatile int firstVal;  
    private volatile int secondVal;  

    //判斷二者是否相等  
    public boolean areValuesEqual(){  
        return ( firstVal == secondVal);  
    }  

    public void run() {  
        try{  
            firstVal = 0;  
            secondVal = 0;  
            workMethod();  
        }catch(InterruptedException x){  
            System.out.println("interrupted while in workMethod()");  
        }  
    }  

    private void workMethod() throws InterruptedException {  
        int val = 1;  
        while (true){  
            stepOne(val);  
            stepTwo(val);  
            val++;  
            Thread.sleep(200);  //再次循環(huán)錢(qián)休眠200毫秒  
        }  
    }  

    //賦值后,休眠300毫秒,從而使線程有機(jī)會(huì)在stepOne操作和stepTwo操作之間被掛起  
    private void stepOne(int newVal) throws InterruptedException{  
        firstVal = newVal;  
        Thread.sleep(300);  //模擬長(zhǎng)時(shí)間運(yùn)行的情況  
    }  

    private void stepTwo(int newVal){  
        secondVal = newVal;  
    }  

    public static void main(String[] args){  
        DeprecatedSuspendResume dsr = new DeprecatedSuspendResume();  
        Thread t = new Thread(dsr);  
        t.start();  

        //休眠1秒,讓其他線程有機(jī)會(huì)獲得執(zhí)行  
        try {  
            Thread.sleep(1000);}   
        catch(InterruptedException x){}  
        for (int i = 0; i < 10; i++){  
            //掛起線程  
            t.suspend();  
            System.out.println("dsr.areValuesEqual()=" + dsr.areValuesEqual());  
            //恢復(fù)線程  
            t.resume();  
            try{   
                //線程隨機(jī)休眠0~2秒  
                Thread.sleep((long)(Math.random()*2000.0));  
            }catch(InterruptedException x){  
                //略  
            }  
        }  
        System.exit(0); //中斷應(yīng)用程序  
    }  
} 

運(yùn)行結(jié)果如下:

http://wiki.jikexueyuan.com/project/java-concurrency/images/result6.png" alt="" />

從 areValuesEqual()返回的值有時(shí)為 true,有時(shí)為 false。以上代碼中,在設(shè)置 firstVal 之后,但在設(shè)置 secondVal 之前,掛起新線程會(huì)產(chǎn)生麻煩,此時(shí)輸出的結(jié)果會(huì)為 false(情況 1),這段時(shí)間不適宜掛起線程,但因?yàn)榫€程不能控制何時(shí)調(diào)用它的 suspend 方法,所以這種情況是不可避免的。

當(dāng)然,即使線程不被掛起(注釋掉掛起和恢復(fù)線程的兩行代碼),如果在 main 線程中執(zhí)行 asr.areValuesEqual()進(jìn)行比較時(shí),恰逢 stepOne 操作執(zhí)行完,而 stepTwo 操作還沒(méi)執(zhí)行,那么得到的結(jié)果同樣可能是 false(情況 2)。

下面我們給出不用上述兩個(gè)方法來(lái)實(shí)現(xiàn)線程掛起和恢復(fù)的策略——設(shè)置標(biāo)志位。通過(guò)該方法實(shí)現(xiàn)線程的掛起和恢復(fù)有一個(gè)很好的地方,就是可以在線程的指定位置實(shí)現(xiàn)線程的掛起和恢復(fù),而不用擔(dān)心其不確定性。

對(duì)于上述代碼的改進(jìn)代碼如下:

public class AlternateSuspendResume extends Object implements Runnable {  

    private volatile int firstVal;  
    private volatile int secondVal;  
    //增加標(biāo)志位,用來(lái)實(shí)現(xiàn)線程的掛起和恢復(fù)  
    private volatile boolean suspended;  

    public boolean areValuesEqual() {  
        return ( firstVal == secondVal );  
    }  

    public void run() {  
        try {  
            suspended = false;  
            firstVal = 0;  
            secondVal = 0;  
            workMethod();  
        } catch ( InterruptedException x ) {  
            System.out.println("interrupted while in workMethod()");  
        }  
    }  

    private void workMethod() throws InterruptedException {  
        int val = 1;  

        while ( true ) {  
            //僅當(dāng)賢臣掛起時(shí),才運(yùn)行這行代碼  
            waitWhileSuspended();   

            stepOne(val);  
            stepTwo(val);  
            val++;  

            //僅當(dāng)線程掛起時(shí),才運(yùn)行這行代碼  
            waitWhileSuspended();   

            Thread.sleep(200);    
        }  
    }  

    private void stepOne(int newVal)   
                    throws InterruptedException {  

        firstVal = newVal;  
        Thread.sleep(300);    
    }  

    private void stepTwo(int newVal) {  
        secondVal = newVal;  
    }  

    public void suspendRequest() {  
        suspended = true;  
    }  

    public void resumeRequest() {  
        suspended = false;  
    }  

    private void waitWhileSuspended()   
                throws InterruptedException {  

        //這是一個(gè)“繁忙等待”技術(shù)的示例。  
        //它是非等待條件改變的最佳途徑,因?yàn)樗鼤?huì)不斷請(qǐng)求處理器周期地執(zhí)行檢查,   
        //更佳的技術(shù)是:使用Java的內(nèi)置“通知-等待”機(jī)制  
        while ( suspended ) {  
            Thread.sleep(200);  
        }  
    }  

    public static void main(String[] args) {  
        AlternateSuspendResume asr =   
                new AlternateSuspendResume();  

        Thread t = new Thread(asr);  
        t.start();  

        //休眠1秒,讓其他線程有機(jī)會(huì)獲得執(zhí)行  
        try { Thread.sleep(1000); }   
        catch ( InterruptedException x ) { }  

        for ( int i = 0; i < 10; i++ ) {  
            asr.suspendRequest();  

            //讓線程有機(jī)會(huì)注意到掛起請(qǐng)求  
            //注意:這里休眠時(shí)間一定要大于  
            //stepOne操作對(duì)firstVal賦值后的休眠時(shí)間,即300ms,  
            //目的是為了防止在執(zhí)行asr.areValuesEqual()進(jìn)行比較時(shí),  
            //恰逢stepOne操作執(zhí)行完,而stepTwo操作還沒(méi)執(zhí)行  
            try { Thread.sleep(350); }   
            catch ( InterruptedException x ) { }  

            System.out.println("dsr.areValuesEqual()=" +   
                    asr.areValuesEqual());  

            asr.resumeRequest();  

            try {   
                //線程隨機(jī)休眠0~2秒  
                Thread.sleep(  
                        ( long ) (Math.random() * 2000.0) );  
            } catch ( InterruptedException x ) {  
                //略  
            }  
        }  

        System.exit(0); //退出應(yīng)用程序  
    }  
} 

運(yùn)行結(jié)果如下:

http://wiki.jikexueyuan.com/project/java-concurrency/images/result7.png" alt="" />

由結(jié)果可以看出,輸出的所有結(jié)果均為 true。首先,針對(duì)情況 1(線程掛起的位置不確定),這里確定了線程掛起的位置,不會(huì)出現(xiàn)線程在 stepOne 操作和 stepTwo 操作之間掛起的情況;針對(duì)情況 2(main 線程中執(zhí)行asr.areValuesEqual()進(jìn)行比較時(shí),恰逢 stepOne 操作執(zhí)行完,而 stepTwo 操作還沒(méi)執(zhí)行),在發(fā)出掛起請(qǐng)求后,還沒(méi)有執(zhí)行 asr.areValuesEqual()操作前,讓 main 線程休眠 450ms(>300ms),如果掛起請(qǐng)求發(fā)出時(shí),新線程正執(zhí)行到或即將執(zhí)行到 stepOne 操作(如果在其前面的話,就會(huì)響應(yīng)掛起請(qǐng)求,從而掛起線程),那么在 stepTwo 操作執(zhí)行前,main 線程的休眠還沒(méi)結(jié)束,從而 main 線程休眠結(jié)束后執(zhí)行 asr.areValuesEqualv操作進(jìn)行比較時(shí),stepTwo 操作已經(jīng)執(zhí)行完,因此也不會(huì)出現(xiàn)輸出結(jié)果為 false 的情況。

可以將 ars.suspendRequest()代碼后的 sleep 代碼去掉,或?qū)⑿菝邥r(shí)間改為 200(明顯小于 300 即可)后,查看執(zhí)行結(jié)果,會(huì)發(fā)現(xiàn)結(jié)果中依然會(huì)有出現(xiàn) false 的情況。如下圖所示:

http://wiki.jikexueyuan.com/project/java-concurrency/images/result8.png" alt="" />

總結(jié):線程的掛起和恢復(fù)實(shí)現(xiàn)的正確方法是:通過(guò)設(shè)置標(biāo)志位,讓線程在安全的位置掛起。

終止線程

當(dāng)調(diào)用 Thread 的 start()方法,執(zhí)行完 run()方法后,或在 run()方法中 return,線程便會(huì)自然消亡。另外 Thread API 中包含了一個(gè) stop()方法,可以突然終止線程。但它在 JDK1.2 后便被淘汰了,因?yàn)樗赡軐?dǎo)致數(shù)據(jù)對(duì)象的崩潰。一個(gè)問(wèn)題是,當(dāng)線程終止時(shí),很少有機(jī)會(huì)執(zhí)行清理工作;另一個(gè)問(wèn)題是,當(dāng)在某個(gè)線程上調(diào)用 stop()方法時(shí),線程釋放它當(dāng)前持有的所有鎖,持有這些鎖必定有某種合適的理由——也許是阻止其他線程訪問(wèn)尚未處于一致性狀態(tài)的數(shù)據(jù),突然釋放鎖可能使某些對(duì)象中的數(shù)據(jù)處于不一致?tīng)顟B(tài),而且不會(huì)出現(xiàn)數(shù)據(jù)可能崩潰的任何警告。

終止線程的替代方法:同樣是使用標(biāo)志位,通過(guò)控制標(biāo)志位來(lái)終止線程。