當一個任務正在運行的過程當中,而咱們卻發現這個任務已經沒有必要繼續運行了,那麼咱們便產生了取消任務的須要。好比 上一篇文章 提到的線程池的 invokeAny
方法,它能夠在線程池中運行一組任務,當其中任何一個任務完成時,invokeAny
方法便會中止阻塞並返回,同時也會 取消其餘任務。那咱們如何取消一個正在運行的任務?java
前面兩篇多線程的文章都有提到 Future<V>
接口和它的一個實現類 FutureTask<V>
,而且咱們已經知道 Future<V>
能夠用來和已經提交的任務進行交互。Future<V>
接口定義了以下幾個方法:segmentfault
get
方法:經過前面文章的介紹,咱們已經瞭解了 get
方法的使用 —— get
方法 用來返回和 Future
關聯的任務的結果。帶參數的 get
方法指定一個超時時間,在超時時間內該方法會阻塞當前線程,直到得到結果 。多線程
TimeoutException
異常;CancellationException
異常);ExecutionException
異常);InterruptedException
異常 —— 注意,當前線程是指調用 get
方法的線程,而不是運行任務的線程)。不帶參數的 get
能夠理解爲超時時間無限大,即一直等待直到得到結果或者出現異常。ide
cancel(boolean mayInterruptIfRunning)
方法:該方法是非阻塞的。經過 JDK 的文檔,咱們能夠知道 該方法即可以用來(嘗試)終止一個任務。測試
cancel
傳入參數爲 true
,那麼便會去終止與 Future
關聯的任務。cancel(false)
與 cancel(true)
的區別在於,cancel(false)
只 取消已經提交但尚未被運行的任務(即任務就不會被安排運行);而 cancel(true)
會取消全部已經提交的任務,包括 正在等待的 和 正在運行的 任務。this
isCancelled
方法:該方法是非阻塞的。在任務結束以前,若是任務被取消了,該方法返回 true
,不然返回 false
;若是任務已經完成,該方法則一直返回 false
。spa
isDone
方法:該方法一樣是非阻塞的。若是任務已經結束(正常結束,或者被取消,或者執行出錯),返回 true
,不然返回 false
。線程
而後咱們來實踐下 Future
的 cancel
方法的功能:3d
import java.util.concurrent.*; public class FutureTest { public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newSingleThreadExecutor(); SimpleTask task = new SimpleTask(3_000); // task 須要運行 3 秒 Future<Double> future = threadPool.submit(task); threadPool.shutdown(); // 發送關閉線程池的指令 double time = future.get(); System.out.format("任務運行時間: %.3f s\n", time); } private static final class SimpleTask implements Callable<Double> { private final int sleepTime; // ms public SimpleTask(int sleepTime) { this.sleepTime = sleepTime; } @Override public Double call() throws Exception { double begin = System.nanoTime(); Thread.sleep(sleepTime); double end = System.nanoTime(); double time = (end - begin) / 1E9; return time; // 返回任務運行的時間,以 秒 計 } } }
運行結果(任務正常運行):
code
而後咱們定義一個用來取消任務的方法:
private static void cancelTask(final Future<?> future, final int delay) { Runnable cancellation = new Runnable() { @Override public void run() { try { Thread.sleep(delay); future.cancel(true); // 取消與 future 關聯的正在運行的任務 } catch (InterruptedException ex) { ex.printStackTrace(System.err); } } }; new Thread(cancellation).start(); }
而後修改 main
方法:
public static void main(String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor(); SimpleTask task = new SimpleTask(3_000); // task 須要運行 3 秒 Future<Double> future = threadPool.submit(task); threadPool.shutdown(); // 發送關閉線程池的指令 cancelTask(future, 2_000); // 在 2 秒以後取消該任務 try { double time = future.get(); System.out.format("任務運行時間: %.3f s\n", time); } catch (CancellationException ex) { System.err.println("任務被取消"); } catch (InterruptedException ex) { System.err.println("當前線程被中斷"); } catch (ExecutionException ex) { System.err.println("任務執行出錯"); } }
運行結果:
能夠看到,當任務被取消時,Future
的 get
方法拋出了 CancellationException
異常,而且成功的取消了任務(從構建(運行)總時間能夠發現)。
這樣就能夠了嗎?調用 Future
的 cancel(true)
就必定能取消正在運行的任務嗎?
咱們來寫一個真正的耗時任務,判斷一個數是否爲素數,測試數據爲 1000000033 (它是一個素數)。
import java.util.concurrent.*; public class FutureTest { public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newSingleThreadExecutor(); long num = 1000000033L; PrimerTask task = new PrimerTask(num); Future<Boolean> future = threadPool.submit(task); threadPool.shutdown(); boolean result = future.get(); System.out.format("%d 是否爲素數? %b\n", num, result); } private static final class PrimerTask implements Callable<Boolean> { private final long num; public PrimerTask(long num) { this.num = num; } @Override public Boolean call() throws Exception { // i < num 讓任務有足夠的運行時間 for (long i = 2; i < num; i++) { if (num % i == 0) { return false; } } return true; } } }
在個人機器上,這個任務須要 13 秒才能運行完畢:
而後咱們修改 main
方法,在任務運行到 2 秒的時候調用 Future
的 cancel(true)
:
public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newSingleThreadExecutor(); long num = 1000000033L; PrimerTask task = new PrimerTask(num); Future<Boolean> future = threadPool.submit(task); threadPool.shutdown(); // 發送關閉線程池的指令 cancelTask(future, 2_000); // 在 2 秒以後取消該任務 try { boolean result = future.get(); System.out.format("%d 是否爲素數? %b\n", num, result); } catch (CancellationException ex) { System.err.println("任務被取消"); } catch (InterruptedException ex) { System.err.println("當前線程被中斷"); } catch (ExecutionException ex) { System.err.println("任務執行出錯"); } }
程序運行到 2 秒時候的輸出:
程序的最終輸出:
能夠發現,雖然咱們取消了任務,Future
的 get
方法也對咱們的取消作出了響應(即拋出 CancellationException
異常),可是任務並無中止,而是直到任務運行完畢了,程序才結束。
查看 Future
的實現類 FutureTask
的源碼,咱們來看一下調用 cancel(true)
究竟發生了什麼:
原來 cancel(true)
方法的原理是向正在運行任務的線程發送中斷指令 —— 即調用運行任務的 Thread
的 interrupt()
方法。
因此 若是一個任務是可取消的,那麼它應該能夠對 Thread
的 interrupt()
方法作出被取消時的響應。
而 Thread
的 isInterrupted()
方法,即可以用來判斷當前 Thread
是否被中斷。任務開始運行時,運行任務的線程確定沒有被中斷,因此 isInterruped()
方法會返回 false
;而 interrupt()
方法調用以後,isInterruped()
方法會返回 true
。
(由此咱們也能夠知道,Thread.sleep
方法是能夠對中斷作出響應的)
因此咱們修改 PrimerTask
的 call
方法,讓其能夠對運行任務的線程被中斷時作出中止運行(跳出循環)的響應:
@Override public Boolean call() throws Exception { // i < num 讓任務有足夠的運行時間 for (long i = 2; i < num; i++) { if (Thread.currentThread().isInterrupted()) { // 任務被取消 System.out.println("PrimerTask.call: 你取消我幹啥?"); return false; } if (num % i == 0) { return false; } } return true; }
運行結果:
能夠看到程序在 2 秒的時候中止了運行,任務被成功取消。
總結:若是要經過 Future
的 cancel
方法取消正在運行的任務,那麼該任務一定是能夠 對線程中斷作出響應 的任務。經過 Thread.currentThread().isInterrupted()
方法,咱們能夠判斷任務是否被取消,從而作出相應的取消任務的響應。