一纬向、為什么要使用線程池迈倍?
??為了充分利用多核計(jì)算機(jī)的性能伤靠,程序需要被設(shè)計(jì)成多線程程序,保證不會(huì)出現(xiàn)某個(gè)CPU很忙啼染,某個(gè)CPU很閑的情況宴合,把線程的創(chuàng)建、通信和管理交給線程池管理迹鹅,可以讓開(kāi)發(fā)人員專注于程序的業(yè)務(wù)邏輯卦洽;另一方面,線程的創(chuàng)建需要消耗操作系統(tǒng)的資源斜棚,如果頻繁地創(chuàng)建和銷毀線程阀蒂,代價(jià)太大该窗,而線程池對(duì)此做了優(yōu)化,線程池中預(yù)先創(chuàng)建好了一組空閑的線程蚤霞,當(dāng)程序運(yùn)行需要時(shí)酗失,從線程池中取用空閑線程,當(dāng)程序運(yùn)行結(jié)束時(shí)争便,工作線程又重置為空閑線程放到線程池中级零,提高系統(tǒng)資源的利用效率。
線程是否越多越好滞乙?
(1)線程在java中是一個(gè)對(duì)象奏纪,更是操作系統(tǒng)的資源,線程的創(chuàng)建斩启、銷毀需要時(shí)間序调,如果創(chuàng)建時(shí)間 + 銷毀時(shí)間 > 任務(wù)執(zhí)行時(shí)間,就不劃算兔簇。
(2)java對(duì)象占用堆內(nèi)存发绢,操作系統(tǒng)線程占用系統(tǒng)內(nèi)存,根據(jù)jvm規(guī)范垄琐,一個(gè)線程默認(rèn)最大棧大小為1M边酒,這個(gè)棧空間是需要從系統(tǒng)內(nèi)存中分配的狸窘,如果線程過(guò)多墩朦,也會(huì)消耗過(guò)多內(nèi)存,當(dāng)超過(guò)負(fù)荷時(shí)程序可能會(huì)發(fā)生異撤埽或錯(cuò)誤氓涣。
(3)線程數(shù)過(guò)多,會(huì)導(dǎo)致操作系統(tǒng)需要頻繁切換上下文陋气,這需要消耗性能劳吠,每個(gè)線程的執(zhí)行效率反而會(huì)下降。
二巩趁、線程池原理
1痒玩、線程池管理器
??用于創(chuàng)建并管理線程池,包括創(chuàng)建線程池议慰、銷毀線程池凰荚,添加新的執(zhí)行任務(wù)等,一般會(huì)限定線程池大小褒脯,避免像沒(méi)有線程池管理時(shí)無(wú)限制地去創(chuàng)建線程導(dǎo)致線程數(shù)過(guò)多導(dǎo)致性能下降的情況便瑟。
2、工作線程
??線程池中的線程番川,當(dāng)應(yīng)用程序需要一個(gè)新的線程時(shí)到涂,線程池為其創(chuàng)建一個(gè)工作線程脊框;當(dāng)該線程需要執(zhí)行的程序結(jié)束時(shí),線程不會(huì)被銷毀践啄,而是回到線程池浇雹,繼續(xù)等待執(zhí)行任務(wù),這就意味著工作線程可以循環(huán)地執(zhí)行不同任務(wù)屿讽。
3昭灵、任務(wù)接口
??每個(gè)提交到線程池的任務(wù)都必須實(shí)現(xiàn)的接口,以供工作線程調(diào)度任務(wù)的執(zhí)行伐谈,它主要規(guī)定了任務(wù)的入口烂完、任務(wù)執(zhí)行完后的收尾工作、任務(wù)的執(zhí)行狀態(tài)等诵棵,可以把任務(wù)接口看成是工作線程執(zhí)行目標(biāo)的一個(gè)格式抠蚣。
4、任務(wù)隊(duì)列
??線程池所能創(chuàng)建的工作線程數(shù)是有限的履澳,當(dāng)提交的任務(wù)數(shù)很多導(dǎo)致線程池暫時(shí)沒(méi)有線程可去執(zhí)行時(shí)嘶窄,線程池會(huì)將其放入到任務(wù)隊(duì)列中,可以把任務(wù)隊(duì)列看成是一種緩沖機(jī)制距贷,等線程池里又有空閑的工作線程里柄冲,線程池就可以從任務(wù)隊(duì)列中取出任務(wù)給工作線程去執(zhí)行。
三忠蝗、線程池API —— 接口定義和實(shí)現(xiàn)類
類型 | 名稱 | 描述 |
---|---|---|
接口 | Executor |
最上層的接口羊初,定義了執(zhí)行任務(wù)的方法execute
|
接口 | ExecutorService |
繼承了Executor 接口,拓展了Callable 什湘、Future 、關(guān)閉方法 |
抽閑實(shí)現(xiàn)類 | AbstractExecutorService |
繼承了ExecutorService 接口晦攒,實(shí)現(xiàn)了submit 闽撤、invokeAny 、invokeAll 方法脯颜,將提交的任務(wù)包裝成RunnableFuture 對(duì)象哟旗,交給子類去執(zhí)行 |
接口 | ScheduledExecutorService |
繼承了ExecutorService ,增加了定時(shí)任務(wù)相關(guān)的方法 |
實(shí)現(xiàn)類 | ThreadPoolExecutor |
繼承了AbstractExecutorService 栋操,基礎(chǔ)闸餐、標(biāo)準(zhǔn)的線程池實(shí)現(xiàn) |
實(shí)現(xiàn)類 | ScheduledThreadPoolExecutor |
繼承了ThreadPoolExecutor ,實(shí)現(xiàn)ScheduledExecutorService 中相關(guān)定時(shí)任務(wù)的方法 |
工具類 | Executors |
創(chuàng)建線程池的工具類 |
1矾芙、Executor
??最上層的執(zhí)行器接口舍沙,定義了執(zhí)行方法execute
,參數(shù)類型為Runnable
表示可以接收提交到線程池Runnable
任務(wù)剔宪,該接口實(shí)現(xiàn)了任務(wù)的提交與任務(wù)的執(zhí)行分離拂铡。
2壹无、ExecutorService
??繼承了Executor
接口,ExecutorService
擴(kuò)展了功能感帅,定義了關(guān)閉線程池
斗锭、提交任務(wù)
、執(zhí)行任一個(gè)或全部任務(wù)
的方法失球,這些方法能產(chǎn)生追蹤一個(gè)或多個(gè)異步任務(wù)進(jìn)度的Future
對(duì)象或列表岖是,接口中定義的方法列表如下:
package java.util.concurrent;
import java.util.List;
import java.util.Collection;
public interface ExecutorService extends Executor {
// 優(yōu)雅關(guān)閉線程池,關(guān)閉前提交的任務(wù)將被執(zhí)行实苞,但不再接受新的任務(wù)
void shutdown();
// 馬上關(guān)閉線程池豺撑,所有正在執(zhí)行中的任務(wù)會(huì)被終止,所有正在等待執(zhí)行的任務(wù)不會(huì)被執(zhí)行硬梁,并隨方法返回
List<Runnable> shutdownNow();
// 判斷線程池是否已關(guān)閉
boolean isShutdown();
// 如果關(guān)閉線程池后前硫,線程池中所有的任務(wù)都已執(zhí)行完成,則返回true荧止;本方法用于判斷線程池是否任務(wù)都執(zhí)行完畢屹电,必須在調(diào)用shutdown()或shutdownNow()后才能使用
boolean isTerminated();
// 檢測(cè)線程池是否已關(guān)閉直到所有的任務(wù)都被執(zhí)行結(jié)束,如果還有任務(wù)正在執(zhí)行跃巡,則會(huì)阻塞等待危号,直到發(fā)生超時(shí),或者當(dāng)前線程被中斷
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
// 提交一個(gè)用于執(zhí)行的Callable返回任務(wù)素邪,并返回一個(gè)Future對(duì)象外莲,可以通過(guò)其get方法獲取Callable執(zhí)行結(jié)果
<T> Future<T> submit(Callable<T> task);
// 提交一個(gè)用于執(zhí)行的Runnable任務(wù),并返回一個(gè)Future對(duì)象兔朦,執(zhí)行結(jié)果會(huì)放入傳入的result中
<T> Future<T> submit(Runnable task, T result);
// 提交一個(gè)Runnable任務(wù)偷线,并返回一個(gè)Future對(duì)象,執(zhí)行結(jié)果為null沽甥,這里返回Future的意義在于可通過(guò)get方法的阻塞控制某些代碼在任務(wù)執(zhí)行結(jié)束之后才執(zhí)行
Future<?> submit(Runnable task);
// 執(zhí)行給定的任務(wù)集合声邦,執(zhí)行完畢后,返回結(jié)果集
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
// 執(zhí)行給定的任務(wù)集合摆舟,執(zhí)行完畢或者超時(shí)后亥曹,其他任務(wù)終止
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
// 執(zhí)行給定的任務(wù),任意一個(gè)執(zhí)行成功則返回結(jié)果恨诱,其他任務(wù)終止
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
// 執(zhí)行給定的任務(wù)媳瞪,任意一個(gè)執(zhí)行成功或者超時(shí)后,則返回結(jié)果照宝,其他任務(wù)終止
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
3蛇受、AbstractExecutorService
??AbstractExecutorService
是ExecutorService
的實(shí)現(xiàn)類,實(shí)現(xiàn)了ExecutorService
接口中定義的submit
厕鹃、invokeAny
龙巨、invokeAll
方法笼呆,執(zhí)行一個(gè)任務(wù)前會(huì)把提交的Runnable
、Callable
任務(wù)通過(guò)newTaskFor
方法轉(zhuǎn)化為RunnableFuture
對(duì)象旨别,再提交給executor
方法執(zhí)行诗赌,executor
方法的具體實(shí)現(xiàn)由AbstractExecutorService
的子類各自去實(shí)現(xiàn)。
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
// implements code
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
// implements code
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
// implements code
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
// implements code
};
4秸弛、ScheduledExecutorService
??一個(gè)能夠根據(jù)傳入的時(shí)延延遲執(zhí)行和周期參數(shù)定期執(zhí)行任務(wù)的ExecutorService
接口铭若,方法定義如下:
public interface ScheduledExecutorService extends ExecutorService {
// 創(chuàng)建并執(zhí)行一個(gè)一次性任務(wù),過(guò)了延遲時(shí)間就會(huì)被執(zhí)行
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
// 創(chuàng)建一個(gè)周期性任務(wù)递览,過(guò)了給定的初始延遲時(shí)間叼屠,會(huì)被第一次執(zhí)行,執(zhí)行過(guò)程中發(fā)生了異常绞铃,那么任務(wù)就停止
// 一個(gè)任務(wù)執(zhí)行時(shí)長(zhǎng)超過(guò)了周期時(shí)間镜雨,下一次任務(wù)會(huì)等到該次任務(wù)執(zhí)行結(jié)束之后立即執(zhí)行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
// 創(chuàng)建一個(gè)周期性任務(wù),過(guò)了給定的初始延遲時(shí)間儿捧,會(huì)被第一次執(zhí)行荚坞,執(zhí)行過(guò)程中發(fā)生了異常,那么任務(wù)就停止
// 一個(gè)任務(wù)執(zhí)行時(shí)長(zhǎng)超過(guò)了周期時(shí)間菲盾,下一次任務(wù)會(huì)等到該次任務(wù)執(zhí)行結(jié)束的時(shí)間基礎(chǔ)上颓影,計(jì)算執(zhí)行延遲
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
【scheduleAtFixedRate
跟scheduleWithFixedDelay
的不同如下】
假設(shè)執(zhí)行周期(delay)為3秒,代表每隔3秒執(zhí)行一次任務(wù)懒鉴,任務(wù)是一個(gè)一個(gè)執(zhí)行的诡挂,不會(huì)出現(xiàn)有兩個(gè)不同周期的任務(wù)同時(shí)執(zhí)行的情況,假設(shè)在第10秒開(kāi)始執(zhí)行任務(wù)临谱,任務(wù)執(zhí)行了4秒璃俗,超過(guò)了周期時(shí)間,那么下個(gè)周期的任務(wù)會(huì)在第幾秒開(kāi)始執(zhí)行悉默?
scheduleAtFixedRate
:在第14秒就馬上開(kāi)始執(zhí)行城豁。
scheduleWithFixedDelay
:在第14秒之后,等3秒到第17秒時(shí)才開(kāi)始執(zhí)行麦牺。
5、ThreadPoolExecutor
??ThreadPoolExecutor
是一個(gè)基礎(chǔ)鞭缭、標(biāo)準(zhǔn)的線程池剖膳,繼承了AbstractExecutorService
,也是最常用岭辣、直接使用的線程池吱晒,ThreadPoolExecutor
構(gòu)造器中主要的參數(shù)有核心線程大小
、最大線程數(shù)
沦童、線程存活時(shí)間
仑濒、存活時(shí)間單位
叹话、線程阻塞隊(duì)列
、線程工廠
墩瞳、拒絕策略
驼壶。
-
核心線程數(shù)
??如果提交的任務(wù)是通過(guò)核心線程來(lái)執(zhí)行的,則不會(huì)受線程存活時(shí)間的限制喉酌。
-
最大線程數(shù)
??創(chuàng)建線程有代價(jià)热凹,系統(tǒng)資源寶貴,不可能無(wú)限地創(chuàng)建線程泪电,所以要設(shè)置最大線程數(shù)般妙;最大線程數(shù)是指包含核心線程數(shù)在內(nèi)的所能創(chuàng)建的最大線程數(shù)。
-
線程存活時(shí)間
??針對(duì)非核心線程設(shè)置的線程存活時(shí)間相速,防止大量非核心線程因阻塞或其他原因?qū)е聢?zhí)行時(shí)間過(guò)長(zhǎng)碟渺,進(jìn)而導(dǎo)致等待執(zhí)行的工作隊(duì)列一直等不到線程來(lái)執(zhí)行。
-
存活時(shí)間單位
??設(shè)置線程存活時(shí)間時(shí)的時(shí)間單位突诬,有納秒苫拍、微秒、毫秒攒霹、秒怯疤、分、時(shí)催束、天集峦。
-
線程工廠
??允許通過(guò)傳入的自定義的實(shí)現(xiàn)了ThreadFactory
接口的類來(lái)創(chuàng)建線程。
-
拒絕策略
??當(dāng)提交任務(wù)過(guò)多抠刺,線程池和工作隊(duì)列的容量都無(wú)法執(zhí)行和緩存所有工作任務(wù)時(shí)塔淤,就需要采取一定的策略拒絕提交的任務(wù)。
定義的具體構(gòu)造器如下:
public class ThreadPoolExecutor extends AbstractExecutorService {
// 使用默認(rèn)線程工廠和拒絕策略的構(gòu)造器速妖,其余參數(shù)指定
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// 使用默認(rèn)拒絕策略的構(gòu)造器高蜂,其余參數(shù)指定
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
// 使用默認(rèn)線程工廠的構(gòu)造器,其余參數(shù)指定
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
// 指定所有參數(shù)的構(gòu)造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
}
??通常調(diào)用submit
方法去提交任務(wù)罕容,接著調(diào)用execute
方法執(zhí)行任務(wù)备恤,execute
的工作流程如下:
(1)判斷線程池中的核心線程數(shù)是否已滿?若未滿锦秒,則直接創(chuàng)建一個(gè)核心工作線程來(lái)執(zhí)行任務(wù)露泊;否則進(jìn)入(2)。
(2)判斷工作隊(duì)列是否已滿旅择?若未滿惭笑,則將新提交的任務(wù)存儲(chǔ)在工作隊(duì)列中;否則進(jìn)入(3)。
(3)判斷工作線程是否已達(dá)最大數(shù)量限制沉噩?沒(méi)達(dá)到捺宗,則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù);否則進(jìn)入(4)川蒙。
(4)執(zhí)行拒絕策略來(lái)處理任務(wù)蚜厉。
【流程圖如下】
【源碼】
【舉例】
??核心線程數(shù)為5,最大線程數(shù)為10(5秒超時(shí))派歌,工作隊(duì)列為3弯囊,現(xiàn)在要提交15個(gè)任務(wù)(假設(shè)幾乎同一時(shí)間提交,忽略提交時(shí)延),每個(gè)任務(wù)的執(zhí)行時(shí)間為6秒,那么線程池執(zhí)行的情況如下:
(1)為提交的前5個(gè)任務(wù)創(chuàng)建核心工作線程嫩海,沒(méi)有超時(shí)時(shí)間,任務(wù)可以執(zhí)行完畢霎烙。
(2)第6到第8個(gè)任務(wù)會(huì)被工作隊(duì)列緩存。
(3)為第9到第13個(gè)任務(wù)創(chuàng)建非核心工作線程蕊连,由于超時(shí)時(shí)間為5秒悬垃,所以這5個(gè)工作線程會(huì)在第5秒時(shí)中斷。
(4)拒絕第14到第15個(gè)提交的任務(wù)甘苍。
【代碼示例】
public class Demo9 {
public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception {
// 測(cè)試: 提交15個(gè)執(zhí)行時(shí)間需要3秒的任務(wù)尝蠕,看超過(guò)大小的2個(gè),對(duì)應(yīng)的處理情況
for (int i = 0; i < 15; i++) {
int n = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("開(kāi)始執(zhí)行:" + n);
Thread.sleep(3000L);
System.err.println("執(zhí)行結(jié)束:" + n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("任務(wù)提交成功 :" + i);
}
// 查看線程數(shù)量载庭,查看隊(duì)列等待數(shù)量
Thread.sleep(500L);
System.out.println("當(dāng)前線程池線程數(shù)量為:" + threadPoolExecutor.getPoolSize());
System.out.println("當(dāng)前線程池等待的數(shù)量為:" + threadPoolExecutor.getQueue().size());
// 等待15秒看彼,查看線程數(shù)量和隊(duì)列數(shù)量(理論上,會(huì)被超出核心線程數(shù)量的線程自動(dòng)銷毀)
Thread.sleep(15000L);
System.out.println("當(dāng)前線程池線程數(shù)量為:" + threadPoolExecutor.getPoolSize());
System.out.println("當(dāng)前線程池等待的數(shù)量為:" + threadPoolExecutor.getQueue().size());
}
/**
* 線程池信息: 核心線程數(shù)量5囚聚,最大數(shù)量10靖榕,隊(duì)列大小3,超出核心線程數(shù)量的線程存活時(shí)間:5秒顽铸,指定拒絕策略
* @throws Exception
*/
private void threadPoolExecutorTest2() throws Exception {
// 創(chuàng)建一個(gè) 核心線程數(shù)量為5茁计,最大數(shù)量為10,等待隊(duì)列最大是3 的線程池,也就是最大容納13個(gè)任務(wù)谓松。
// 默認(rèn)的策略是拋出RejectedExecutionException異常星压,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("有任務(wù)被拒絕執(zhí)行了");
}
});
testCommon(threadPoolExecutor);
// 預(yù)計(jì)結(jié)果:
// 1、5個(gè)任務(wù)直接分配線程開(kāi)始執(zhí)行
// 2鬼譬、3個(gè)任務(wù)進(jìn)入等待隊(duì)列
// 3娜膘、隊(duì)列不夠用,臨時(shí)加開(kāi)5個(gè)線程來(lái)執(zhí)行任務(wù)(5秒沒(méi)活干就銷毀)
// 4拧簸、隊(duì)列和線程池都滿了劲绪,剩下2個(gè)任務(wù)男窟,沒(méi)資源了盆赤,被拒絕執(zhí)行
// 5贾富、任務(wù)執(zhí)行,5秒后牺六,如果五任務(wù)可執(zhí)行颤枪,銷毀臨時(shí)創(chuàng)建的5個(gè)線程
}
public static void main(String[] args) throws Exception {
Demo9 demo = new Demo9();
demo.threadPoolExecutorTest2();
}
}
【執(zhí)行結(jié)果】
(1) 如果沒(méi)有指定任務(wù)等待隊(duì)列的長(zhǎng)度,則默認(rèn)為無(wú)界隊(duì)列淑际,不管多少任務(wù)提交都會(huì)被緩存畏纲,這時(shí)其實(shí)拒絕策略已經(jīng)不起作用了,可以直接不指定春缕。
【策略分析】
??在某些場(chǎng)景下盗胀,不急著提交的任務(wù)馬上被執(zhí)行,只要提交的任務(wù)被放入等待執(zhí)行的隊(duì)列中锄贼,由線程池慢慢處理即可票灰,這種情況適合將阻塞隊(duì)列設(shè)置為無(wú)界隊(duì)列(或者容量比較大的隊(duì)列),這樣當(dāng)線程池已滿時(shí)宅荤,提交的任務(wù)會(huì)被緩存到隊(duì)列中而不會(huì)被拒絕執(zhí)行屑迂,因此設(shè)置為無(wú)界隊(duì)列時(shí),不管是否指定拒絕策略冯键,都不會(huì)拒絕惹盼。
【代碼示例】
public class Demo9 {
/**
* 測(cè)試: 提交15個(gè)執(zhí)行時(shí)間需要3秒的任務(wù),看線程池的狀況
*
* @param threadPoolExecutor 傳入不同的線程池,看不同的結(jié)果
* @throws Exception
*/
public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception {
// code
}
/***
* 1惫确、線程池信息: 核心線程數(shù)量5手报,最大數(shù)量10,無(wú)界隊(duì)列雕薪,超出核心線程數(shù)量的線程存活時(shí)間: 5秒昧诱,緩存提交任務(wù)的工作隊(duì)列(這里沒(méi)有指定隊(duì)列大小,就是無(wú)界隊(duì)列所袁,可以無(wú)限緩存任務(wù))
*/
private void threadPoolExecutorTest1() throws Exception {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
testCommon(threadPoolExecutor);
}
public static void main(String[] args) throws Exception {
Demo9 demo = new Demo9();
demo.threadPoolExecutorTest1();
}
}
【執(zhí)行結(jié)果】
(2) 如果任務(wù)提交的頻率不可控不可預(yù)估盏档,也不想堆積緩存而是盡快任務(wù),則
a. 核心線程數(shù)設(shè)為0
b. 線程數(shù)設(shè)一個(gè)比較大的值
c. 用同步隊(duì)列作為阻塞隊(duì)列
【策略解析】
??一般我們不會(huì)把核心線程數(shù)設(shè)置得過(guò)大燥爷,因?yàn)楹诵木€程執(zhí)行完任務(wù)之后是不會(huì)被銷毀而是一直存活的蜈亩,如果核心線程數(shù)設(shè)置過(guò)大,那么當(dāng)某個(gè)時(shí)間點(diǎn)提交了大量的任務(wù)時(shí)前翎,就會(huì)相應(yīng)創(chuàng)建大量的核心線程稚配,當(dāng)這些任務(wù)被執(zhí)行完后,核心線程還存活港华,而后面可能任務(wù)提交的頻率沒(méi)這么高了道川,用不了這么多核心線程,這時(shí)太多核心線程會(huì)占用太多系統(tǒng)資源,系統(tǒng)性能會(huì)受到影響冒萄。
??這時(shí)適合把線程數(shù)量設(shè)大一點(diǎn)臊岸,而核心線程數(shù)不能設(shè)置得太大,某個(gè)時(shí)間點(diǎn)任務(wù)提交頻率又很高尊流,這時(shí)核心線程對(duì)于整個(gè)執(zhí)行任務(wù)的貢獻(xiàn)比例很小帅戒,干脆直接設(shè)置為0即可,這就意味著實(shí)際上執(zhí)行任務(wù)的線程都不會(huì)核心線程崖技,在任務(wù)執(zhí)行完之后線程會(huì)被銷毀逻住,系統(tǒng)資源會(huì)被回收,雖然創(chuàng)建銷毀的代價(jià)也很大迎献,但是跟維護(hù)大量存活的核心線程相比瞎访,還是好得多;而且使用非核心線程使得線程池具有了更好的彈性吁恍,任務(wù)多我就多創(chuàng)建線性装诡,任務(wù)少我就少創(chuàng)建線程,可以滿足任務(wù)提交頻率不可控不定的場(chǎng)景践盼。
??我們不想堆積任務(wù)鸦采,就意味著不應(yīng)該緩存任務(wù),而是直接為任務(wù)創(chuàng)建工作線程咕幻,因此適合用同步隊(duì)列作為阻塞隊(duì)列渔伯,因?yàn)橥疥?duì)列不會(huì)真正緩存任務(wù),而是維護(hù)一組提交任務(wù)的線程肄程,在任務(wù)入隊(duì)offer
時(shí)會(huì)失敗锣吼,進(jìn)而觸發(fā)線程池為提交任務(wù)新增工作線程的效果。
【代碼示例】
/**
* 4蓝厌、 線程池信息:
* 核心線程數(shù)量0玄叠,最大數(shù)量Integer.MAX_VALUE,SynchronousQueue隊(duì)列拓提,超出核心線程數(shù)量的線程存活時(shí)間:60秒
*
* @throws Exception
*/
private void threadPoolExecutorTest4() throws Exception {
// SynchronousQueue读恃,實(shí)際上它不是一個(gè)真正的隊(duì)列,因?yàn)樗粫?huì)為隊(duì)列中元素維護(hù)存儲(chǔ)空間代态。與其他隊(duì)列不同的是寺惫,它維護(hù)一組線程,這些線程在等待著把元素加入或移出隊(duì)列蹦疑。
// 在使用SynchronousQueue作為工作隊(duì)列的前提下西雀,客戶端代碼向線程池提交任務(wù)時(shí),
// 而線程池中又沒(méi)有空閑的線程能夠從SynchronousQueue隊(duì)列實(shí)例中取一個(gè)任務(wù)歉摧,
// 那么相應(yīng)的offer方法調(diào)用就會(huì)失斖щ取(即任務(wù)沒(méi)有被存入工作隊(duì)列)腔呜。
// 此時(shí),ThreadPoolExecutor會(huì)新建一個(gè)新的工作者線程用于對(duì)這個(gè)入隊(duì)列失敗的任務(wù)進(jìn)行處理(假設(shè)此時(shí)線程池的大小還未達(dá)到其最大線程池大小maximumPoolSize)再悼。
// 和Executors.newCachedThreadPool()一樣的
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
testCommon(threadPoolExecutor);
// 預(yù)計(jì)結(jié)果:
// 1育谬、 線程池線程數(shù)量為:15,超出數(shù)量的任務(wù)帮哈,其他的進(jìn)入隊(duì)列中等待被執(zhí)行
// 2、 所有任務(wù)執(zhí)行結(jié)束锰镀,60秒后娘侍,如果無(wú)任務(wù)可執(zhí)行,所有線程全部被銷毀泳炉,池的大小恢復(fù)為0
Thread.sleep(60000L);
System.out.println("60秒后憾筏,再看線程池中的數(shù)量:" + threadPoolExecutor.getPoolSize());
}
【執(zhí)行結(jié)果】
(3) 如果提交的任務(wù)想定時(shí)執(zhí)行,則適用
ScheduledThreadPoolExecutor
【策略分析】
??ScheduledThreadPoolExecutor
實(shí)現(xiàn)了ScheduledExecutorService
接口花鹅,使用ScheduledThreadPoolExecutor
能滿足定時(shí)任務(wù)的需求氧腰;需要注意的地方是,如果定時(shí)任務(wù)中同一時(shí)間要執(zhí)行的任務(wù)很多刨肃,則應(yīng)該把線程數(shù)設(shè)大一點(diǎn)古拴,因?yàn)闉榱吮WC設(shè)定的時(shí)間一到時(shí)線程池中必須有可以執(zhí)行任務(wù)的線程;ScheduledThreadPoolExecutor
中的線程全部都是核心線程真友,因?yàn)槭褂枚〞r(shí)任務(wù)的場(chǎng)景意味著任務(wù)被不斷周期性執(zhí)行黄痪,并且不必立即執(zhí)行,任務(wù)提交的頻率相對(duì)穩(wěn)定盔然,所以使用核心線程利用率高桅打,相對(duì)劃算。
??ScheduledThreadPoolExecutor
沒(méi)有提供指定阻塞隊(duì)列的構(gòu)造參數(shù)愈案,是因?yàn)槠涞讓訉?shí)現(xiàn)的原理依賴于DelayWorkQueue
延時(shí)隊(duì)列挺尾,任務(wù)被提交到延時(shí)隊(duì)列之后,必須在設(shè)定的延時(shí)時(shí)間后才能取出任務(wù)給線程執(zhí)行站绪。
【代碼示例:只延遲不定期執(zhí)行的任務(wù)】
/**
* 5遭铺、 定時(shí)執(zhí)行線程池信息:3秒后執(zhí)行,一次性任務(wù)恢准,到點(diǎn)就執(zhí)行 <br/>
* 核心線程數(shù)量5掂僵,最大數(shù)量Integer.MAX_VALUE,DelayedWorkQueue延時(shí)隊(duì)列顷歌,超出核心線程數(shù)量的線程存活時(shí)間:0秒
*
* @throws Exception
*/
private void threadPoolExecutorTest5() throws Exception {
// 和Executors.newScheduledThreadPool()一樣的
ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
threadPoolExecutor.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任務(wù)被執(zhí)行锰蓬,現(xiàn)在時(shí)間:" + System.currentTimeMillis());
}
}, 3000, TimeUnit.MILLISECONDS);
System.out.println(
"定時(shí)任務(wù),提交成功眯漩,時(shí)間是:" + System.currentTimeMillis() + ", 當(dāng)前線程池中線程數(shù)量:" + threadPoolExecutor.getPoolSize());
// 預(yù)計(jì)結(jié)果:任務(wù)在3秒后被執(zhí)行一次
}
【執(zhí)行結(jié)果】
【代碼示例:延遲定期的任務(wù)】
??定期執(zhí)行任務(wù)有兩種形式芹扭,一種是使用
scheduleAtFixedRate
麻顶,假設(shè)某次定時(shí)任務(wù)執(zhí)行的時(shí)間超過(guò)了周期時(shí)間,則下一次執(zhí)行會(huì)在上一次執(zhí)行結(jié)束之后馬上執(zhí)行舱卡;另一種是使用scheduleWithFixedDelay
辅肾,不管定時(shí)任務(wù)執(zhí)行的時(shí)間是否超過(guò)周期時(shí)間,下一次執(zhí)行都會(huì)在上一次執(zhí)行結(jié)束之后等待固定的周期時(shí)間之后才開(kāi)始執(zhí)行轮锥。如果周期時(shí)間大于任務(wù)執(zhí)行時(shí)間矫钓,則兩種定期任務(wù)的的效果都一樣。??下面的代碼中舍杜,設(shè)置了延遲時(shí)間為2秒新娜,定時(shí)周期為1秒,每個(gè)任務(wù)的執(zhí)行時(shí)間為3秒既绩,這就意味著概龄,當(dāng)一個(gè)周期過(guò)去時(shí)要開(kāi)始新的周期時(shí),上個(gè)周期的任務(wù)還沒(méi)被執(zhí)行完畢饲握,兩種方式都不會(huì)在上個(gè)周期的任務(wù)還沒(méi)執(zhí)行完畢是又開(kāi)始執(zhí)行新的周期任務(wù)私杜;對(duì)于
scheduleAtFixedRate
方式的線程池來(lái)說(shuō),會(huì)在3秒任務(wù)執(zhí)行完畢后救欧,馬上執(zhí)行新的周期任務(wù)衰粹,對(duì)于scheduleWithFixedDelay
方式的線程池,會(huì)在3秒任務(wù)執(zhí)行完畢之后笆怠,再等待1秒鐘才開(kāi)始執(zhí)行新的周期任務(wù)寄猩。
/**
* 6、 定時(shí)執(zhí)行線程池信息:線程固定數(shù)量5 骑疆,<br/>
* 核心線程數(shù)量5田篇,最大數(shù)量Integer.MAX_VALUE,DelayedWorkQueue延時(shí)隊(duì)列箍铭,超出核心線程數(shù)量的線程存活時(shí)間:0秒
*
* @throws Exception
*/
private void threadPoolExecutorTest6() throws Exception {
ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
// 周期性執(zhí)行某一個(gè)任務(wù)泊柬,線程池提供了兩種調(diào)度方式,這里單獨(dú)演示一下诈火。測(cè)試場(chǎng)景一樣兽赁。
// 測(cè)試場(chǎng)景:提交的任務(wù)需要3秒才能執(zhí)行完畢±涫兀看兩種不同調(diào)度方式的區(qū)別
// 效果1: 提交后刀崖,2秒后開(kāi)始第一次執(zhí)行,之后每間隔1秒拍摇,固定執(zhí)行一次(如果發(fā)現(xiàn)上次執(zhí)行還未完畢亮钦,則等待完畢,完畢后立刻執(zhí)行)充活。
// 也就是說(shuō)這個(gè)代碼中是蜂莉,3秒鐘執(zhí)行一次(計(jì)算方式:每次執(zhí)行三秒蜡娶,間隔時(shí)間1秒,執(zhí)行結(jié)束后馬上開(kāi)始下一次執(zhí)行映穗,無(wú)需等待)
threadPoolExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任務(wù)-1 被執(zhí)行窖张,現(xiàn)在時(shí)間:" + System.currentTimeMillis());
}
}, 2000, 1000, TimeUnit.MILLISECONDS);
// 效果2:提交后,2秒后開(kāi)始第一次執(zhí)行蚁滋,之后每間隔1秒宿接,固定執(zhí)行一次(如果發(fā)現(xiàn)上次執(zhí)行還未完畢,則等待完畢辕录,等上一次執(zhí)行完畢后再開(kāi)始計(jì)時(shí)睦霎,等待1秒)。
// 也就是說(shuō)這個(gè)代碼鐘的效果看到的是:4秒執(zhí)行一次踏拜。 (計(jì)算方式:每次執(zhí)行3秒,間隔時(shí)間1秒低剔,執(zhí)行完以后再等待1秒速梗,所以是 3+1)
threadPoolExecutor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任務(wù)-2 被執(zhí)行,現(xiàn)在時(shí)間:" + System.currentTimeMillis());
}
}, 2000, 1000, TimeUnit.MILLISECONDS);
}
【執(zhí)行結(jié)果:?jiǎn)为?dú)執(zhí)行scheduleAtFixedRate
方式表現(xiàn)出來(lái)的效果是間隔3秒襟齿;
??????單獨(dú)執(zhí)行scheduleWithFixedDelay
方式表現(xiàn)出來(lái)的效果是間隔4秒】
(4)終止提交任務(wù)
??有時(shí)候姻锁,我們?cè)诔绦驁?zhí)行的某個(gè)節(jié)點(diǎn)終止線程池,終止線程池之后不會(huì)再接收新的任務(wù)猜欺,當(dāng)線程池和緩存隊(duì)列已滿或者線程池已終止時(shí)位隶,都會(huì)執(zhí)行拒絕策略。如果想在終止時(shí)开皿,讓終止前已經(jīng)提交的任務(wù)繼續(xù)執(zhí)行完畢涧黄,則使用shutdown
方法;如果想讓所有任務(wù)都終止赋荆,并且正在執(zhí)行的任務(wù)也要嘗試中斷笋妥,則使用shutdownNow
方法。
【代碼示例:使用shutdown
關(guān)閉線程池】
/**
* 7窄潭、 終止線程:線程池信息: 核心線程數(shù)量5春宣,最大數(shù)量10,隊(duì)列大小3嫉你,超出核心線程數(shù)量的線程存活時(shí)間:5秒月帝, 指定拒絕策略的
*
* @throws Exception
*/
private void threadPoolExecutorTest7() throws Exception {
// 創(chuàng)建一個(gè) 核心線程數(shù)量為5,最大數(shù)量為10,等待隊(duì)列最大是3 的線程池幽污,也就是最大容納13個(gè)任務(wù)嚷辅。
// 默認(rèn)的策略是拋出RejectedExecutionException異常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("有任務(wù)被拒絕執(zhí)行了");
}
});
// 測(cè)試: 提交15個(gè)執(zhí)行時(shí)間需要3秒的任務(wù)距误,看超過(guò)大小的2個(gè)潦蝇,對(duì)應(yīng)的處理情況
for (int i = 0; i < 15; i++) {
int n = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("開(kāi)始執(zhí)行:" + n);
Thread.sleep(3000L);
System.err.println("執(zhí)行結(jié)束:" + n);
} catch (InterruptedException e) {
System.out.println("異常:" + e.getMessage());
}
}
});
System.out.println("任務(wù)提交成功 :" + i);
}
// 1秒后終止線程池
Thread.sleep(1000L);
threadPoolExecutor.shutdown();
// 再次提交提示失敗
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println("追加一個(gè)任務(wù)");
}
});
// 結(jié)果分析
// 1款熬、 10個(gè)任務(wù)被執(zhí)行,3個(gè)任務(wù)進(jìn)入隊(duì)列等待攘乒,2個(gè)任務(wù)被拒絕執(zhí)行
// 2贤牛、調(diào)用shutdown后,不接收新的任務(wù)则酝,等待13任務(wù)執(zhí)行結(jié)束
// 3殉簸、 追加的任務(wù)在線程池關(guān)閉后,無(wú)法再提交沽讹,會(huì)被拒絕執(zhí)行
}
【執(zhí)行結(jié)果】
【代碼示例:使用shutdownNow
關(guān)閉線程池】
/**
* 8般卑、 立刻終止線程:線程池信息: 核心線程數(shù)量5,最大數(shù)量10爽雄,隊(duì)列大小3蝠检,超出核心線程數(shù)量的線程存活時(shí)間:5秒, 指定拒絕策略的
*
* @throws Exception
*/
private void threadPoolExecutorTest8() throws Exception {
// 創(chuàng)建一個(gè) 核心線程數(shù)量為5挚瘟,最大數(shù)量為10,等待隊(duì)列最大是3 的線程池叹谁,也就是最大容納13個(gè)任務(wù)。
// 默認(rèn)的策略是拋出RejectedExecutionException異常乘盖,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("有任務(wù)被拒絕執(zhí)行了");
}
});
// 測(cè)試: 提交15個(gè)執(zhí)行時(shí)間需要3秒的任務(wù)焰檩,看超過(guò)大小的2個(gè),對(duì)應(yīng)的處理情況
for (int i = 0; i < 15; i++) {
int n = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("開(kāi)始執(zhí)行:" + n);
Thread.sleep(3000L);
System.err.println("執(zhí)行結(jié)束:" + n);
} catch (InterruptedException e) {
System.out.println("異常:" + e.getMessage());
}
}
});
System.out.println("任務(wù)提交成功 :" + i);
}
// 1秒后終止線程池
Thread.sleep(1000L);
List<Runnable> shutdownNow = threadPoolExecutor.shutdownNow();
// 再次提交提示失敗
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println("追加一個(gè)任務(wù)");
}
});
System.out.println("未結(jié)束的任務(wù)有:" + shutdownNow.size());
// 結(jié)果分析
// 1订框、 10個(gè)任務(wù)被執(zhí)行析苫,3個(gè)任務(wù)進(jìn)入隊(duì)列等待,2個(gè)任務(wù)被拒絕執(zhí)行
// 2穿扳、調(diào)用shutdownnow后衩侥,隊(duì)列中的3個(gè)線程不再執(zhí)行,10個(gè)線程被終止
// 3矛物、 追加的任務(wù)在線程池關(guān)閉后顿乒,無(wú)法再提交,會(huì)被拒絕執(zhí)行
}
【執(zhí)行結(jié)果】
6泽谨、Executors
??Executors
是一個(gè)工具類璧榄,提供了創(chuàng)建各種線程池的方法,下面的代碼是等同的:
(1) Executors.newFixedThreadPool(int nThreads)
= ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
【效果】
??創(chuàng)建了一個(gè)指定核心線程數(shù)吧雹,無(wú)非核心工作線程骨杂,任務(wù)緩存隊(duì)列無(wú)限的線程池。
【源碼】
(2)
Executors.newCachedThreadPool()
= new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
【效果】
??創(chuàng)建了一個(gè)核心線程為0雄卷,最大線程數(shù)為Integer最大值搓蚪,使用同步隊(duì)列的線程池。
【源碼】
(3)
Executors.newScheduledThreadPool(int corePoolSize)
= new ScheduledThreadPoolExecutor(int corePoolSize)
【效果】
??創(chuàng)建一個(gè)指定核心線程數(shù)的定期任務(wù)線程池丁鹉。
【源碼】
三妒潭、總結(jié)對(duì)比一覽
1悴能、幾種常用線程池對(duì)比
線程池創(chuàng)建 | 核心線程數(shù) | 最大線程數(shù) | 隊(duì)列 | 適用場(chǎng)景 |
---|---|---|---|---|
Executors.newFixedThreadPool(int nThreads) |
nThread | nThread | 阻塞隊(duì)列,大小無(wú)界 | 任務(wù)提交頻率穩(wěn)定 |
Executors.newCachedThreadPool() |
0 | Integer.MAX_VALUE | 同步隊(duì)列雳灾,不緩存任務(wù) | 任務(wù)提交頻率不定 |
Executors.newScheduledThreadPool(int corePoolSize) |
corePoolSize | corePoolSize | 無(wú)界延時(shí)隊(duì)列 | 定時(shí)任務(wù) |
2漠酿、兩種定時(shí)任務(wù)對(duì)比
方法 | 策略 |
---|---|
scheduleAtFixedRate |
如果任務(wù)執(zhí)行時(shí)間超過(guò)定時(shí)周期,則下一次任務(wù)執(zhí)行在上一次任務(wù)執(zhí)行結(jié)束之后馬上開(kāi)始 |
scheduleWithFixedDelay |
如果任務(wù)執(zhí)行時(shí)間超過(guò)定時(shí)周期谎亩,則下一次任務(wù)執(zhí)行在上一次任務(wù)執(zhí)行結(jié)束之后再等待一個(gè)周期的時(shí)間再開(kāi)始 |
3炒嘲、兩種關(guān)閉線程池對(duì)比
方法 | 是否允許提交新任務(wù) | 是否執(zhí)行等待隊(duì)列中的任務(wù) | 是否嘗試中斷正在執(zhí)行的任務(wù) |
---|---|---|---|
shutdown |
否 | 是 | 否 |
shutdownNow |
否 | 是 | 是 |