摘要:比如上一篇文章提到的線程池的方法,它可以在線程池中運行一組任務,當其中任何一個任務完成時,方法便會停止阻塞并返回,同時也會取消其他任務。
當一個任務正在運行的過程中,而我們卻發(fā)現(xiàn)這個任務已經沒有必要繼續(xù)運行了,那么我們便產生了取消任務的需要。比如 上一篇文章 提到的線程池的 invokeAny 方法,它可以在線程池中運行一組任務,當其中任何一個任務完成時,invokeAny 方法便會停止阻塞并返回,同時也會 取消其他任務。那我們如何取消一個正在運行的任務?
前面兩篇多線程的文章都有提到 Future
get 方法:通過前面文章的介紹,我們已經了解了 get 方法的使用 —— get 方法 用來返回和 Future 關聯(lián)的任務的結果。帶參數(shù)的 get 方法指定一個超時時間,在超時時間內該方法會阻塞當前線程,直到獲得結果 。
如果在給定的超時時間內沒有獲得結果,那么便拋出 TimeoutException 異常;
或者執(zhí)行的任務被取消(此時拋出 CancellationException 異常);
或者執(zhí)行任務時出錯,即執(zhí)行過程中出現(xiàn)異常(此時拋出 ExecutionException 異常);
或者當前線程被中斷(此時拋出 InterruptedException 異常 —— 注意,當前線程是指調用 get 方法的線程,而不是運行任務的線程)。
不帶參數(shù)的 get 可以理解為超時時間無限大,即一直等待直到獲得結果或者出現(xiàn)異常。
cancel(boolean mayInterruptIfRunning) 方法:該方法是非阻塞的。通過 JDK 的文檔,我們可以知道 該方法便可以用來(嘗試)終止一個任務。
如果任務運行之前調用了該方法,那么任務就不會被運行;
如果任務已經完成或者已經被取消,那么該方法方法不起作用;
如果任務正在運行,并且 cancel 傳入參數(shù)為 true,那么便會去終止與 Future 關聯(lián)的任務。
cancel(false) 與 cancel(true)的區(qū)別在于,cancel(false) 只 取消已經提交但還沒有被運行的任務(即任務就不會被安排運行);而 cancel(true) 會取消所有已經提交的任務,包括 正在等待的 和 正在運行的 任務。
isCancelled 方法:該方法是非阻塞的。在任務結束之前,如果任務被取消了,該方法返回 true,否則返回 false;如果任務已經完成,該方法則一直返回 false。
isDone 方法:該方法同樣是非阻塞的。如果任務已經結束(正常結束,或者被取消,或者執(zhí)行出錯),返回 true,否則返回 false。
然后我們來實踐下 Future 的 cancel 方法的功能:
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 秒 Futurefuture = threadPool.submit(task); threadPool.shutdown(); // 發(fā)送關閉線程池的指令 double time = future.get(); System.out.format("任務運行時間: %.3f s ", time); } private static final class SimpleTask implements Callable { 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; // 返回任務運行的時間,以 秒 計 } } }
運行結果(任務正常運行):
然后我們定義一個用來取消任務的方法:
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 關聯(lián)的正在運行的任務 } 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 秒 Futurefuture = threadPool.submit(task); threadPool.shutdown(); // 發(fā)送關閉線程池的指令 cancelTask(future, 2_000); // 在 2 秒之后取消該任務 try { double time = future.get(); System.out.format("任務運行時間: %.3f s ", time); } catch (CancellationException ex) { System.err.println("任務被取消"); } catch (InterruptedException ex) { System.err.println("當前線程被中斷"); } catch (ExecutionException ex) { System.err.println("任務執(zhí)行出錯"); } }
運行結果:
可以看到,當任務被取消時,Future 的 get 方法拋出了 CancellationException 異常,并且成功的取消了任務(從構建(運行)總時間可以發(fā)現(xiàn))。
這樣就可以了嗎?調用 Future 的 cancel(true) 就一定能取消正在運行的任務嗎?
我們來寫一個真正的耗時任務,判斷一個數(shù)是否為素數(shù),測試數(shù)據(jù)為 1000000033 (它是一個素數(shù))。
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); Futurefuture = threadPool.submit(task); threadPool.shutdown(); boolean result = future.get(); System.out.format("%d 是否為素數(shù)? %b ", num, result); } private static final class PrimerTask implements Callable { 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); Futurefuture = threadPool.submit(task); threadPool.shutdown(); // 發(fā)送關閉線程池的指令 cancelTask(future, 2_000); // 在 2 秒之后取消該任務 try { boolean result = future.get(); System.out.format("%d 是否為素數(shù)? %b ", num, result); } catch (CancellationException ex) { System.err.println("任務被取消"); } catch (InterruptedException ex) { System.err.println("當前線程被中斷"); } catch (ExecutionException ex) { System.err.println("任務執(zhí)行出錯"); } }
程序運行到 2 秒時候的輸出:
程序的最終輸出:
可以發(fā)現(xiàn),雖然我們取消了任務,Future 的 get 方法也對我們的取消做出了響應(即拋出 CancellationException 異常),但是任務并沒有停止,而是直到任務運行完畢了,程序才結束。
查看 Future 的實現(xiàn)類 FutureTask 的源碼,我們來看一下調用 cancel(true) 究竟發(fā)生了什么:
原來 cancel(true) 方法的原理是向正在運行任務的線程發(fā)送中斷指令 —— 即調用運行任務的 Thread 的 interrupt() 方法。
所以 如果一個任務是可取消的,那么它應該可以對 Thread 的 interrupt() 方法做出被取消時的響應。
而 Thread 的 isInterrupted() 方法,便可以用來判斷當前 Thread 是否被中斷。任務開始運行時,運行任務的線程肯定沒有被中斷,所以 isInterruped() 方法會返回 false;而 interrupt() 方法調用之后,isInterruped() 方法會返回 true。
(由此我們也可以知道,Thread.sleep 方法是可以對中斷做出響應的)
所以我們修改 PrimerTask 的 call 方法,讓其可以對運行任務的線程被中斷時做出停止運行(跳出循環(huán))的響應:
@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() 方法,我們可以判斷任務是否被取消,從而做出相應的取消任務的響應。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/66430.html
摘要:本人郵箱歡迎轉載轉載請注明網址代碼已經全部托管有需要的同學自行下載引言前面我們講了那么多有關線程的知識不知道讀者有沒有想過這么一個問題如果有這么一個比較耗時的任務必須使用線程來執(zhí)行但是在這個任務執(zhí)行完之后我需要得到這個線程的返回值以目前我們 本人郵箱: 歡迎轉載,轉載請注明網址 http://blog.csdn.net/tianshi_kcogithub: https://github...
摘要:本文首發(fā)于一世流云的專欄一模式簡介模式是多線程設計模式中的一種常見模式,它的主要作用就是異步地執(zhí)行任務,并在需要的時候獲取結果。二中的模式在多線程基礎之模式中,我們曾經給出過模式的通用類關系圖。 showImg(https://segmentfault.com/img/bVbiwcx?w=1000&h=667); 本文首發(fā)于一世流云的專欄:https://segmentfault.co...
摘要:本文只介紹中線程池的基本使用,不會過多的涉及到線程池的原理。可緩存線程的線程池創(chuàng)建一個可緩存線程的線程池。首先是從接口繼承到的方法使用該方法即將一個任務交給線程池去執(zhí)行。方法方法的作用是向線程池發(fā)送關閉的指令。 首先,我們?yōu)槭裁葱枰€程池?讓我們先來了解下什么是 對象池 技術。某些對象(比如線程,數(shù)據(jù)庫連接等),它們創(chuàng)建的代價是非常大的 —— 相比于一般對象,它們創(chuàng)建消耗的時間和內存都...
摘要:類提供了一些有用的方法在線程池中執(zhí)行內的任務。在線程池提交任務后返回了一個對象,使用它可以知道任務的狀態(tài)和得到返回的執(zhí)行結果。 Callable和Future出現(xiàn)的原因 創(chuàng)建線程的2種方式,一種是直接繼承Thread,另外一種就是實現(xiàn)Runnable接口。 這2種方式都有一個缺陷就是:在執(zhí)行完任務之后無法獲取執(zhí)行結果。 如果需要獲取執(zhí)行結果,就必須通過共享變量或者使用線程通信的方式來達...
摘要:本文的源碼基于。人如其名,包含了和兩部分。而將一個任務的狀態(tài)設置成終止態(tài)只有三種方法我們將在下文的源碼解析中分析這三個方法。將棧中所有掛起的線程都喚醒后,下面就是執(zhí)行方法這個方法是一個空方 前言 系列文章目錄 有了上一篇對預備知識的了解之后,分析源碼就容易多了,本篇我們就直接來看看FutureTask的源碼。 本文的源碼基于JDK1.8。 Future和Task 在深入分析源碼之前,我...
閱讀 2895·2023-04-26 02:49
閱讀 3462·2021-11-25 09:43
閱讀 3445·2021-10-09 09:43
閱讀 3021·2021-09-28 09:44
閱讀 2461·2021-09-22 15:29
閱讀 4542·2021-09-14 18:02
閱讀 2795·2021-09-03 10:48
閱讀 3440·2019-08-30 12:47