1. 程式人生 > >Java併發程式設計系列之七:正確終止與恢復執行緒

Java併發程式設計系列之七:正確終止與恢復執行緒

前面提到了stop()、suspend()等方法在終止與恢復執行緒的弊端,那麼問題來了,應該如何正確終止與恢復執行緒呢?這裡可以使用兩種方法:interrupt()方法和使用boolean變數進行控制。

在使用interrupt方法之前,有必要介紹一下中斷以及與interrupt相關的方法。中斷可以理解為執行緒的一個標誌位屬性,表示一個執行中的執行緒是否被其他執行緒進行了中斷操作。這裡提到了其他執行緒,所以可以認為中斷是執行緒之間進行通訊的一種方式,簡單來說就是由其他執行緒通過執行interrupt方法對該執行緒打個招呼,讓起中斷標誌位為true,從而實現中斷執行緒執行的目的。

其他執行緒呼叫了interrupt方法後,該執行緒通過檢查自身是否被中斷進行響應,具體就是該執行緒需要呼叫isInterrupted方法進行判斷是否被中斷或者呼叫Thread類的靜態方法interrupted對當前執行緒的中斷標誌位進行復位(變為false)。需要注意的是,如果該執行緒已經處於終結狀態,即使該執行緒被中斷過,那麼呼叫isInterrupted方法返回仍然是false,表示沒有被中斷。

那麼是不是執行緒呼叫了interrupt方法對該執行緒進行中斷,該執行緒就會被中斷呢?答案是否定的。因為Java虛擬機器對會丟擲InterruptedException異常的方法進行了特別處理:Java虛擬機器會將該執行緒的中斷標誌位清除,然後跑出InterruptedException,這個時候呼叫isInterrupted方法返回的也是false

下面的程式碼首先建立了兩個執行緒,一個執行緒內部不停睡眠,另一個則不斷執行,然後對這兩個執行緒執行中斷操作。

package com.rhwayfun.concurrency;

/**
 * Created by rhwayfun on 16-4-2.
 */
public class Interrupted { public static void main(String[] args){ //建立一個休眠執行緒 Thread sleepThread = new Thread(new SleepThread(),"SleepThread"); //設為守護執行緒 sleepThread.setDaemon(true); //建立一個忙執行緒 Thread busyThread = new Thread(new BusyThread(),"BusyThread"
); //把該執行緒設為守護執行緒 //守護執行緒只有當其他前臺執行緒全部退出之後才會結束 busyThread.setDaemon(true); //啟動休眠執行緒 sleepThread.start(); //啟動忙執行緒 busyThread.start(); //休眠5秒,讓兩個執行緒充分執行 SleepUtil.second(5); //嘗試中斷執行緒 //只需要呼叫interrupt方法 sleepThread.interrupt(); busyThread.interrupt(); //檢視這兩個執行緒是否被中斷了 System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted()); System.out.println("BusyThread interrupted is " + busyThread.isInterrupted()); //防止sleepThread和busyThread立刻退出 SleepUtil.second(2); } /** * 不斷休眠 */ static class SleepThread implements Runnable{ public void run() { while (true){ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 不斷等待 */ static class BusyThread implements Runnable{ public void run() { while (true){ //忙等待 } } } }

執行結果:

這裡寫圖片描述

可以發現內部不停睡眠的方法執行執行中斷後,其中斷標誌位返回的是false,而一直執行的執行緒的中斷標誌位則為true。這裡主要由於Sleep方法會丟擲InterruptedException異常,所以Java虛擬機器把SleepThread的中斷標誌位復位了,所以才會顯示false。

那麼使用interrupt方法正確終止執行緒已經很明顯了,程式碼如下:

package com.rhwayfun.concurrency;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Created by rhwayfun on 16-4-2.
 */
public class SafeShutdownThread {
    public static void main(String[] args) throws InterruptedException {
        DateFormat format = new SimpleDateFormat("HH:mm:ss");
        Runner one = new Runner();
        //建立第一個計數執行緒,該執行緒使用jdk自帶的中斷方法執行中斷
        Thread threadOne = new Thread(one,"ThreadOne");
        //執行第一個執行緒
        threadOne.start();
        //threadOne休眠一秒,然後由main thread執行中斷
        TimeUnit.SECONDS.sleep(1);
        threadOne.interrupt();
        System.out.println("ThreadOne is interrupted ? " + threadOne.isInterrupted());
        System.out.println("main thread interrupt ThreadOne at " + format.format(new Date()));

        //建立第二個執行緒,該執行緒使用cancel方法執行中斷
        Runner two = new Runner();
        Thread threadTwo = new Thread(two,"ThreadTwo");
        threadTwo.start();
        //休眠一秒,然後呼叫cancel方法中斷執行緒
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
        System.out.println("ThreadTwo is interrupted ? " + threadTwo.isInterrupted());
        System.out.println("main thread interrupt ThreadTwo at " + format.format(new Date()));
    }

    /**
     * 該執行緒是一個計數執行緒
     */
    private static class Runner implements Runnable{
        //變數i
        private long i;
        //是否繼續執行的標誌
        //這裡使用volatile關鍵字可以保證多執行緒併發訪問該變數的時候
        //其他執行緒都可以感知到該變數值的變化。這樣所有執行緒都會從共享
        //記憶體中取值
        private volatile boolean on = true;
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()){
                i++;
            }
            System.out.println("Count i = " + i);
        }

        //讓執行緒終止的方法
        public void cancel(){
            on = false;
        }
    }
}

在計數執行緒中通過使用一個boolean變數成功終止了執行緒。這種通過標誌位或者中斷操作的方式能夠使得執行緒在終止的時候有機會去清理資源,而不是武斷地將執行緒終止,因此這種終止執行緒的做法更優雅和安全。

上面的程式只是正確地終止了執行緒,卻沒有給出正確恢復的方法。可能有人會想到:再寫一個方法讓on變數為true不就行了。事實並如此,因為在CountThread中,由於已經呼叫cancel方法,這時on變數已經是false了,執行緒按照順序執行原則繼續執行,所以即使改變on為true也是沒用的,因為CountThread已經終止了。具體的解決方法將在下一篇關於等待通知機制的文章給出詳細的解決措施。