1. 程式人生 > >Java如何停止執行緒

Java如何停止執行緒

Preface

啟動一個執行緒或任務都是很簡單,執行緒一般在任務結束後便會自行停止。但是有時我們希望能夠線上程自行停止前能夠停止它們,比如一些取消操作,或者是應用程式需要快速關閉。博主日前就遇到了這樣的問題。

但是在《JAVA併發程式設計實踐》一書中指出:

Java沒有提供任何機制,來安全地強迫停止手頭地工作。

 

一般來講,對於Runnable來說,需要做的任務都是在run方法裡面進行的,停止一個執行緒也可以認為是從run方法跳出來吧。

執行緒停止

使用標誌位

這種方式的基本思想為:使用一個標誌位,通過這個標誌位來決定run方法是繼續執行還是直接結束。如下:

  static class CancelledTaggedRunnnable implements Runnable{
        private volatile boolean cancelled = false;
        @Override
        public void run() {
            while(!cancelled){
             //沒有停止需要做什麼操作
            }
            //執行緒停止後需要幹什麼
            System.out.println("任務結束了");
        }
        public void cancell(){
            cancelled = true;
        }
    }

定義一個標誌位:cancelled,cancelled為true是即run方法結束,反之繼續while裡面的任務。通過cancell方法來停止任務。

寫一個測試類:

 @Test
    public void testCancelledTaggedRunnnable() throws InterruptedException {
        CancelledTaggedRunnnable taggedRunnnable = new CancelledTaggedRunnnable();
        Thread thread = new Thread(taggedRunnnable);
        thread.start();

        System.err.println(thread.isAlive());

        Thread.sleep(1000);
        taggedRunnnable.cancell();

        Thread.sleep(1000);
        System.err.println(thread.isAlive());

        Thread.sleep(1000);
        System.err.println(thread.isAlive());
    }

上述程式碼的操作為:啟動該執行緒,1s秒呼叫cancell方法,從而來停止該執行緒。結果如下

 

 

中斷策略

但是使用標誌位這種方法有個很大的侷限性,那就是通過迴圈來使每次的操作都需要檢查一下標誌位。

Java還提供了中斷協作機制,能夠使一個執行緒要求另外一個執行緒停止當前工作。其大致的思想為:呼叫執行緒Thread的interrupt方法,線上程內部通過捕獲InterruptedException異常來決定執行緒是否繼續還是退出。如下:

static class InterruptRunnable implements Runnable{

        private BlockingQueue queue = new ArrayBlockingQueue(10);
        @Override
        public void run() {
            int i= 0;
            for (;;) {
                try {
                    //執行緒的操作
                    i++;
                    queue.put(i);
                } catch (InterruptedException e) {
                    //捕獲到了異常 該怎麼做
                    System.out.println(queue);
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

上述程式碼通過BlockingQueue的put方法來丟擲InterruptedException異常。當內部捕獲到該異常時,從而決定是否繼續還是直接退出了。

寫個測試方法:

@Test
    public void testInterruptRunnable() throws InterruptedException {
        InterruptRunnable runnable = new InterruptRunnable();
        Thread thread = new Thread(runnable);
        thread.start();

        System.err.println(thread.isAlive());

        Thread.sleep(1000);
        thread.interrupt();

        Thread.sleep(1000);
        System.err.println(thread.isAlive());

        Thread.sleep(1000);
        System.err.println(thread.isAlive());
    }

該測試方法大致同使用標誌位的測試方法,同樣啟動該執行緒後,1秒後呼叫執行緒的interrupt方法,從而觸發Runnable的內部queue.put(i)操作丟擲InterruptedException異常。   測試結果如下:

強行停止(不推薦)

對於多執行緒,使用中斷策略 較為優雅,也是官方的推薦。執行緒停止前你應該做一些操作,從而保證該執行緒執行後的資料不會被丟失,這一點在多執行緒中極為重要。

但是對於中斷策略還是有一個很大的缺陷,那就是,必須通過中斷的阻塞函式,如:我使用的BlockingQueue的put方法,也可以是Thread.sleep()方法,才能丟擲InterruptedException。如果拋不出這樣的異常呢?

對於單執行緒,還有一個簡單粗暴的方式,那就是Java已經不再使用的Thread  stop方法。

使用方法即直接呼叫:thread.stop()即可。

如果你對該執行緒的操作不再關心了,對結果也不再在意了,使用該方法也是可以的。

但多執行緒情況下,或者執行緒帶鎖的情況下 那就要慎用了。該方法不安全

使用future任務

 

Java還提供一個可帶返回值的執行緒操作,FutureTask。在這個類中可以呼叫其cancel方法來取消這個任務。

你可以設定true或者false來決定是否讓執行緒出現中斷的操作。從而可以使這個中斷後能夠捕獲InterruptedException來決定是否讓操作結束。

一般來說,這個類都是跟帶返回值的Callable介面一起使用,Runnable介面也是可以使用的,定義執行緒的操作:

static class MyRunnable implements Runnable{
        @Override
        public void run(){
            for(;;){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    System.out.println("執行緒被中斷");
                    return;
                }
                System.out.println("程式執行中....");
            }
        }
    }

上述程式碼較簡單,不需要多說了。使用FutureTask進行測試,先不讓產生中斷:

 @Test
    public void test01() throws Exception {
        FutureTask task = new FutureTask<>(new MyRunnable(), 0);
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool.submit(task);

        Thread.sleep(1000);
        System.out.println("任務是否完成?"+task.isDone());
        System.out.println("任務是否被取消?"+task.isCancelled());

        Thread.sleep(1000);
        //在這裡取消操作
        System.out.println(task.cancel(true));

        Thread.sleep(1000);
        System.out.println("任務是否完成?"+task.isDone());
        System.out.println("任務是否被取消?"+task.isCancelled());

        Thread.sleep(5000);

    }

通過執行緒池的方式來提交任務,讓其執行,2秒後取消任務,檢視控制檯:

但是FutureTask的cancelle方法並不能真正的停止當前執行緒,其主要思想還是如同中斷策略。也就是說,如果我們不設定task.cancell(true),那麼任務還不會被中斷,如,我們改為task.cancell(false),結果如下:

雖然此時呼叫了task.cancell(false)方法,但是通過isDone,isCancelled方法並不能說名任務就結束了。如上,任務還在繼續。

 

綜上,Java並沒有停止執行緒的方法,如果需要手動的停止,還是建議較優雅的中斷策略,或者FutureTask。