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。