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