Java中正確終止線程的方法

Thread類中有一個已經廢棄的 stop() 方法,它能夠終止線程,但因爲它無論三七二十一,直接終止線程,因此被廢棄了。好比,當線程被中止後還須要進行一些善後操做(如,關閉外部資源),使用這個方法就無能爲力了。能夠經過線程中斷來實現線程終止。java

 

首先來看一下Java線程中斷的一些內容:ide

  • Java平臺爲每一個線程維護了一個布爾型的中斷標記,能夠經過下列方法獲取該標記的值:
    • interrupt() 中斷某個線程
    • isInterrupted() 返回該線程的中斷標記
    • interrupted() 返回並重置該線程的中斷標記(置爲false)
  • 中斷僅是發起線程對目標線程的一種請求,也就是說,目標線程對這種請求能夠相應,也能夠忽略。
  • Java標準庫中與線程阻塞相關的方法對中斷的相應方式都是拋出 InterruptedException 異常,而且按照慣例,拋出異常前都會重置中斷標記爲false,所以這些方法會清空線程的中斷標記
  • Java標準庫中與線程阻塞相關的方法在進行阻塞前會判斷中斷標記是否爲true,爲true則拋出異常;若是在阻塞後調用中斷方法的話,那麼JVM會設置該線程的中斷標記,而後將該線程喚醒,所以中斷具備喚醒線程的做用

 

由上面幾點和第二句加粗的話可知,可使用線程中斷來實現線程終止,只要目標線程判斷一下中斷標記便可,即便被中斷的線程正處於阻塞狀態,也能把他喚醒起來終止;由第一句加粗的話可知,直接使用線程中斷實現線程終止是存在風險的,由於可能調用了一些Java標準庫的阻塞方法,而致使了中斷標記被清空,也就沒法得到中斷標記了(老是false),所以須要本身建立一箇中斷標記配合使用。測試

 

如,下面是一個可中斷的任務執行器,他會在每次執行任務前,判斷一下自定i的終止標記和剩餘的任務數(善後);提供的shutdown方法除了將工做線程中斷外(主要做用是喚醒可能處於阻塞狀態的任務),還會將終止交集 terminated 置爲 true。this

 

執行 main 方法,能夠發現,首先會打印出「客戶端調用了 shutdown 方法」,而後過了四秒,main線程纔會終止,可知shutdown方法正確地將目標線程終止了。關於「按照慣例,Java標準庫中拋出InterruptedException異常的和線程相關的阻塞方法會清空中斷標記」,能夠將條件中的 !interminated 替換成 !Thread.currentThread().isInterrupted(),而後再執行main方法測試,能夠發現main線程始終沒法終止,由於 sleep() 方法清空了中斷標記,因此  !Thread.currentThread().isInterrupted() 始終爲true,致使工做線程始終沒法終止。線程

 

public class TerminableTaskRunner {
    // 存儲要執行的任務
    private final BlockingQueue<Runnable> tasks;
    // 線程終止標誌
    private volatile boolean terminated;
    // 剩餘的任務數
    private final AtomicInteger count;
    // 實際執行任務的線程
    private volatile Thread workThread;

    public TerminableTaskRunner(int capacity) {
        this.tasks = new LinkedBlockingDeque<>(capacity);
        this.count = new AtomicInteger(0);
        this.workThread = new WorkThread();
        workThread.start();
    }

    public void submit(Runnable task) {
        this.tasks.add(task);
        this.count.incrementAndGet();
    }

    public void shutdown() {
        terminated = true; // 線程終止標誌,因爲中斷標誌可能會被覆蓋,因此須要本身建立一個標誌
        if (workThread != null)
            workThread.interrupt(); // 喚醒線程
    }

    private class WorkThread extends Thread {
        @Override
        public void run() {
            Runnable task;
            try {
                while (!terminated || tasks.size() >= 1) {
                    task = tasks.take();
                    try {
                        task.run(); // 可能會清空當前線程的中斷標記,如task.run()在內部調用的阻塞方法拋出了InterruptedException
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                    count.decrementAndGet();
                }
            } catch (InterruptedException e) {
                // 一旦調用shutdown且tasks.take()阻塞住,就拋出該異常,沒有任務要執行,直接終止
                workThread = null;
            }
        }
    }

    public static void main(String[] args) {
        TerminableTaskRunner taskRunner = new TerminableTaskRunner(4);
        for (int i = 0; i < 4; i++) {
            taskRunner.submit(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    System.out.println("客戶端調用了 shutdown 方法");
                }
            });
        }
        taskRunner.shutdown();

    }
}
相關文章
相關標籤/搜索