線程池的優(yōu)勢(shì):
- 通過復(fù)用已有的線程法精,降低線程創(chuàng)建的銷毀的系統(tǒng)開銷
- 提高響應(yīng)速度多律,復(fù)用已有的線程避免了創(chuàng)建線程的開銷
- 方便線程數(shù)量的管控,如果創(chuàng)建的線程過多搂蜓,咋可能導(dǎo)致系統(tǒng)化新能的下降或者oom的發(fā)生狼荞。、
- 線程池提供了定時(shí)等功能帮碰,并且方便創(chuàng)建
我們可以使用new ThreadPoolExecutor()來創(chuàng)建一個(gè)線程池
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 這個(gè)參數(shù)意思是核心線程的數(shù)量相味,他們一直存在于線程池中,即使處于閑置狀態(tài)也不會(huì)被銷毀殉挽,除非設(shè)置了allowCoreThreadTimeOut這個(gè)參數(shù)丰涉,則在閑置狀態(tài)超過了這個(gè)參數(shù)keepAliveTime 則被銷毀
- maximumPoolSize 線程池中的最大線程數(shù)量,如果活動(dòng)的線程數(shù)超過這個(gè)值的話斯碌,后續(xù)的線程就會(huì)被阻塞一死。 最大數(shù)量指的是核心線程數(shù)量和非核心線程數(shù)量
- keepAliveTime 非核心線程閑置存在的時(shí)長,意思當(dāng)非核心線程閑置時(shí)間超過這個(gè)值就會(huì)被回收
- TimeUnit 是keepAliveTime的時(shí)間單位傻唾,是個(gè)枚舉類型投慈,包含TimeUnit.MILLISECONDS 毫秒,SECONDS秒冠骄,天等等
-
workQueue 阻塞隊(duì)列伪煤。 所有提交的Runnable線程都存放到該隊(duì)列中,常用的阻塞隊(duì)列有:
- threadFactory 線程 工廠凛辣,為線程池提供線程的創(chuàng)建抱既,一般使用默認(rèn)的即可Executors.defaultThreadFactory()
- handler 當(dāng)任務(wù)隊(duì)列已滿,并且線程池達(dá)到了最大線程數(shù)的時(shí)候規(guī)定了接下來的線程該如何處理蟀给。
ThreadPoolExecutor提供了多鐘構(gòu)造方法蝙砌,我們可以使用他默認(rèn)的一些方法來創(chuàng)建,比如:
ThreadPoolExecutor executor = new
ThreadPoolExecutor(
10, 10, 1000, TimeUnit.SECONDS,
new LinkedBlockingDeque<>());
ThreadPoolExecutor 提供了execute和submit兩個(gè)方法提交線程任務(wù)跋理,比如:
executor.submit(()->{});
executor.execute(()->{});
但是submit提供了一分返回值择克,所以如果們用Callable的話,那么我們就可以獲得一個(gè)返回值前普,如果不知道Callable請(qǐng)看我的另外一篇文章 《Java第三種線程創(chuàng)建方法》
Future<String> submit = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "";
}
});
submit.get();
如果使用Lambda更簡單:
Future<String> submit = executor.submit(() -> "")
submit.get(); 可以得到call中返回的值肚邢。
其實(shí)系統(tǒng)為我們提供了集中線程池的創(chuàng)建,所以不用直接使用ThreadPoolExecutor這個(gè)類來創(chuàng)建線程池:
newFixedThreadPool
Executors.newFixedThreadPool(2);
//源碼
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newFixedThreadPool,固定線程數(shù)量的線程池骡湖,核心線程池?cái)?shù)和非核心線程數(shù)量相等并且由自己定義贱纠,不存在超時(shí)機(jī)制,任務(wù)隊(duì)列理論是上可以無限大
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
可以看到 核心線程數(shù)為0响蕴,而線程池的最大數(shù)量可以認(rèn)為無限大谆焊,超時(shí)時(shí)間60秒,并且是不緩存線程的SynchronousQueue隊(duì)列浦夷,所以可以來多少線程就去執(zhí)行多少線程辖试,沒有任務(wù)了60秒之后就全部銷毀
ScheduledExecutorService
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
定時(shí)執(zhí)行線程任務(wù)的線程池:核心線程數(shù)由自己定義,最大線程數(shù)量為無限大劈狐,超時(shí)間為0秒罐孝,所以非核心線程閑置立刻被銷毀,用到了延遲隊(duì)列肥缔,可以設(shè)置線程延遲多少時(shí)間執(zhí)行莲兢,或者多長時(shí)間重復(fù)一次,比如:
scheduledExecutorService.schedule(() -> {}, 10, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(()->{}, 20, 10, TimeUnit.SECONDS);
第一表示10秒之后執(zhí)行線程续膳,第二個(gè)表示20秒之后每10秒執(zhí)行一次線程
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到核心線程數(shù)和最大線程數(shù)都是1改艇,其實(shí)就是單線程了,所以也就不用考慮并發(fā)啊什么的了姑宽。
線程池的執(zhí)行過程
線程池執(zhí)行的過程也挺有意思:
- 提交一個(gè)線程遣耍,如果核心線程數(shù)量沒有滿的話,就啟動(dòng)一個(gè)線程來執(zhí)行炮车。
- 如果核心線程已滿,那么就插入到阻塞隊(duì)列里面去
- 如果阻塞隊(duì)列已經(jīng)滿的話酣溃,就啟動(dòng)一個(gè)非核心線程
- 如果非核心線程也滿了瘦穆,那就調(diào)用handler,報(bào)異成尥悖或者給替換掉了
原本我以為扛或,如果核心線程已滿,就會(huì)立即啟動(dòng)非核心線程來執(zhí)行線程碘饼,實(shí)際上是加到阻塞隊(duì)列里面熙兔,只有阻塞隊(duì)列滿了之后,才會(huì)啟動(dòng)非核心線程艾恼∽∩妫可以簡單的舉個(gè)例子:
ThreadPoolExecutor service = new ThreadPoolExecutor(2, 10,
1000, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
service.execute(() -> {
try {
Thread.sleep(2000);
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
try {
Thread.sleep(2000);
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
System.out.println(3);
});
上面定義了一個(gè)2個(gè)核心線程數(shù)量和2個(gè)非核心線程數(shù)量的線程池,然后提交3個(gè)線程钠绍,前兩個(gè)沉睡2秒舆声,結(jié)果總是:1 3 2
所以是等到核心線程執(zhí)行結(jié)束又執(zhí)行的第3個(gè)線程。
那么對(duì)于ScheduledExecutorService 這個(gè)線程池如果也是符合這個(gè)理論的話,豈不是設(shè)置的定時(shí)可能永遠(yuǎn)不會(huì)執(zhí)行到了么媳握? 我們做個(gè)試驗(yàn):
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.schedule(()->{
try {
Thread.sleep(3000);
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, TimeUnit.MILLISECONDS );
service.schedule(()->{
try {
Thread.sleep(3000);
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, TimeUnit.MILLISECONDS );
service.schedule(()-> System.out.println(3), 1000, TimeUnit.MILLISECONDS );
但是結(jié)果卻始終是 3 1 2. 所以對(duì)于這個(gè)定時(shí)的線程池來說這個(gè)理論并不符合
最后當(dāng)應(yīng)用退出碱屁,別忘了關(guān)系線程池:
service.shutdown();
service.shutdownNow();
service.isShutdown();
其中isShutdown表示線程池關(guān)閉是否完成。
shutdown 表示中斷所有沒有執(zhí)行的線程任務(wù)
shutdownNow表示中斷執(zhí)行所有任務(wù)線程蛾找,包括正在執(zhí)行的線程娩脾,他會(huì)有一個(gè)返回值,返回的是那些沒有執(zhí)行的線程列表