上篇文章講了下線程的創(chuàng)建及一些常用的方法镰惦,但是在使用的時(shí)候,大多數(shù)是采用了線程池來(lái)管理線程的創(chuàng)建犬绒,運(yùn)行旺入,銷毀等過(guò)程。本篇將著重講線程池的基礎(chǔ)內(nèi)容凯力,包括通過(guò)線程池創(chuàng)建線程茵瘾,線程池的基本信息等。
創(chuàng)建線程
前期準(zhǔn)備
本小節(jié)所有代碼都是在
CreateThreadByPool
類上咐鹤,該類還有一個(gè)內(nèi)部類MyThread
實(shí)現(xiàn)了Runnable
接口拗秘。
首先先把基本的代碼給寫(xiě)出來(lái)
public class CreateThreadByPool {
public static void main(String[] args) {
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " processing");
process();
System.out.println(Thread.currentThread().getName() + " end");
}
private void process() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return String.format("MyThread{%s}", Thread.currentThread().getName());
}
}
先來(lái)大概回顧一下,當(dāng)我們想創(chuàng)建10個(gè)線程的時(shí)候的代碼普通方式是怎樣的
private static void createThreadByNormalWay() {
for (int i = 0; i < 10; i++) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
在能看到的代碼中祈惶,是使用了start()
自己直接開(kāi)啟了線程雕旨,但是如果用線程池方式來(lái)呢
通過(guò)Executors
第一種創(chuàng)建線程池的方法是通過(guò)Executors
類的靜態(tài)方法來(lái)構(gòu)建,通過(guò)這種方式總共可以創(chuàng)建4種線程池捧请。
并且可以發(fā)現(xiàn)返回是ExecutorService
凡涩,所以還要接受返回值,最后通過(guò)execute
來(lái)啟動(dòng)線程
private static void createThreadByPool() {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
MyThread myThread = new MyThread();
executorService.execute(myThread);
}
}
先不管底層是如何實(shí)現(xiàn)的疹蛉,至少代碼上是把線程交給了線程池來(lái)執(zhí)行活箕,這樣能夠保證線程能夠統(tǒng)一管理。
簡(jiǎn)單的比喻就是前者是要你自己去找班長(zhǎng)簽到可款,后者是班長(zhǎng)統(tǒng)一管理這整個(gè)班的簽到育韩。在main函數(shù)中調(diào)用看看普通方法和通過(guò)線程池創(chuàng)建的線程有什么區(qū)別
可以很明顯的看到有以下幾點(diǎn)區(qū)別
- 線程的名字都不一樣
- 并且普通方式是創(chuàng)建了10個(gè)線程,而后者只是創(chuàng)建了5個(gè)線程(是由我們自己設(shè)定的)
- 前者基本上是10個(gè)線程都是同時(shí)處理筑舅,后者是最多只能處理5個(gè)線程座慰,需要等線程執(zhí)行完有空閑才能處理其它線程。
通過(guò)ThreadPoolExecutor
除了使用Executors.newFixedThreadPool()
創(chuàng)建線程池翠拣,還可以通過(guò)new ThreadPoolExecutor()
版仔,這里可能有的小伙伴會(huì)迷糊了,怎么上面放回的類是ExecutorService
,現(xiàn)在返回的又是ThreadPoolExecutor
蛮粮,其實(shí)兩者是同一個(gè)東西益缎。
可以看到ThreadExecutorPool
是繼承了 AbstractExecutorService
,而后者是實(shí)現(xiàn)了ExecutorService
然想。通過(guò)該方法創(chuàng)建的線程池的代碼如下
可以先這樣運(yùn)行體驗(yàn)下莺奔,至于說(shuō)構(gòu)造函數(shù)里面不同參數(shù)的含義,在后面的篇幅中會(huì)說(shuō)到变泄,到時(shí)候再返回來(lái)看即可令哟。
private static void createThreadByThreadPoolExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
for (int i = 0; i < 10; i++) {
MyThread myThread = new MyThread();
executor.execute(myThread);
}
}
看下運(yùn)行結(jié)果
輸出結(jié)果沒(méi)啥好講的,但是如果細(xì)心的小伙伴在上一個(gè)gif就會(huì)發(fā)現(xiàn)妨蛹,通過(guò)線程池來(lái)啟動(dòng)線程的方式屏富,程序并沒(méi)有退出,會(huì)一直運(yùn)行蛙卤。這是因?yàn)槲覀儧](méi)有shutdown
線程池狠半。
兩者區(qū)別
回過(guò)頭來(lái)看看Executors.靜態(tài)方法
這種方法來(lái)創(chuàng)建線程池的源碼
可以看到其實(shí)更深一層還是使用了new ThreadPoolExecutor()
,只不過(guò)我們自己能定制的構(gòu)造函數(shù)的參數(shù)變得極其少颤难,這時(shí)候肯定有小伙伴疑問(wèn)了神年,那為什么不直接都用new ThreadPoolExecutor()
呢?
《阿里java開(kāi)發(fā)手冊(cè)》 嵩山版明確規(guī)定了兩點(diǎn)行嗤,一是線程資源必須通過(guò)線程池提供已日,不允許自行顯式創(chuàng)建線程;二是線程池不允許使用Executors去創(chuàng)建昂验,而是通過(guò)ThreadPoolExecutor的方式去創(chuàng)建捂敌。
著重看第二點(diǎn)強(qiáng)制通過(guò)ThreadPoolExecutor的方式來(lái)創(chuàng)建線程,原因在下面也有既琴,來(lái)看看FixedThreadPool和SingleThreadPool的源碼
其它的不管,可以看到兩者調(diào)用構(gòu)造函數(shù)中的隊(duì)列都是LinkedBlockingQueue
泡嘴,這個(gè)隊(duì)列是無(wú)邊界的甫恩,所以有了允許請(qǐng)求長(zhǎng)度為Integer.MAX_VALUE
,會(huì)堆積大量的請(qǐng)求 酌予,從而導(dǎo)致OOM磺箕。
再來(lái)看看CachedThreadPool的源碼
注意這里構(gòu)造函數(shù)的第二個(gè)參數(shù)是線程池最大線程數(shù),它設(shè)置成了Integer.MAX_VALUE
抛虫,這就可能會(huì)創(chuàng)建大量的線程松靡,從而導(dǎo)致OOM。
線程池信息
ThreadPoolExecutor
上面也可以看到建椰,創(chuàng)建線程池最重要也是最應(yīng)該使用的方法的是new ThreadPoolExecutor()
雕欺,接下來(lái)把重點(diǎn)放在ThreadPoolExecutor
這個(gè)類上面
這個(gè)是類中的所有的屬性,接下來(lái)再看看構(gòu)造函數(shù)
有4種,但是歸根結(jié)底只有以下這一種構(gòu)造函數(shù)屠列,講下這些參數(shù)的意義啦逆,然后大家就可以回頭看下上一小節(jié)的例子。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//省略實(shí)現(xiàn)
}
corePoolSize
:核心線程數(shù)笛洛,大白話就是能夠工作的線程數(shù)量maximumPoolSize
:最大線程數(shù)夏志,就是這個(gè)線程池能容納線程的數(shù)量keepAliveTime
:存活時(shí)間,當(dāng)線程池中的線程數(shù)量大于核心線程數(shù)的時(shí)候苛让,如果時(shí)候沒(méi)有任務(wù)提交沟蔑,核心線程池外的線程不會(huì)立即被銷毀,而是會(huì)等待狱杰,直到等待的時(shí)間超過(guò)了這個(gè)字段才會(huì)被回收銷毀unit
:存活時(shí)間的單位workQueue
:工作隊(duì)列瘦材,就是在線程開(kāi)始被調(diào)用前,就是存在這個(gè)隊(duì)列中threadFactory
:線程工廠浦旱,執(zhí)行程序創(chuàng)建新線程時(shí)使用的工廠handler
:拒絕策略宇色,當(dāng)達(dá)到線程邊界和隊(duì)列容量而采取的拒絕策略
對(duì)于這個(gè)拒絕策略,簡(jiǎn)單說(shuō)下颁湖,有四種實(shí)現(xiàn)宣蠕。
實(shí)現(xiàn)
RejectedExecutionHandler
接口就能實(shí)現(xiàn)自己的拒絕策略
監(jiān)控線程
下面就來(lái)簡(jiǎn)單實(shí)現(xiàn)一個(gè)自己的拒絕策略,并且來(lái)看下上述類中屬性的信息
首先需要一個(gè)監(jiān)控線程類
class MonitorThread implements Runnable {
//注入一個(gè)線程池
private ThreadPoolExecutor executor;
public MonitorThread(ThreadPoolExecutor executor) {
this.executor = executor;
}
private boolean monitor = true;
public void stopMonitor() {
monitor = false;
}
@Override
public void run() {
//監(jiān)控一直運(yùn)行甥捺,每3s輸出一次狀態(tài)
while (monitor) {
//主要邏輯是監(jiān)控線程池的狀態(tài)
System.out.println(
String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s, rejectedExecutionHandler: %s",
this.executor.getPoolSize(),
this.executor.getCorePoolSize(),
this.executor.getActiveCount(),
this.executor.getCompletedTaskCount(),
this.executor.getTaskCount(),
this.executor.isShutdown(),
this.executor.isTerminated(),
this.executor.getRejectedExecutionHandler()));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同時(shí)實(shí)現(xiàn)自定義的拒絕策略
其實(shí)這還是沒(méi)有對(duì)r處理抢蚀,拒絕了就拒絕了,只是打印出來(lái)镰禾,但是并沒(méi)有實(shí)質(zhì)性地處理
class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("task is rejected");
}
}
接下來(lái)就是public類TheradPoolInfo
皿曲,注意工作線程采用的是上一小節(jié)的MyThread
類
public class ThreadPoolInfo {
public static void main(String[] args) throws InterruptedException {
//新建了一個(gè)線程池,核心線程數(shù)是3吴侦,最大線程數(shù)是5屋休,30s
//隊(duì)列是ArrayBlockingQueue,并且大小邊界是3备韧,拒絕策略自定義輸出一句話
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new MyRejectedExecutionHandler());
//開(kāi)啟監(jiān)控線程
MonitorThread monitorThread = new MonitorThread(executor);
new Thread(monitorThread).start();
//開(kāi)啟工作線程
for (int i = 0; i < 10; i++) {
executor.execute(new MyThread());
}
//關(guān)閉線程池和監(jiān)控線程
Thread.sleep(12000);
executor.shutdown();
Thread.sleep(3000);
monitorThread.stopMonitor();
}
}
預(yù)期結(jié)果: 通過(guò)構(gòu)造函數(shù)可以知道劫樟,預(yù)期是有3個(gè)核心線程執(zhí)行任務(wù),會(huì)拒絕2個(gè)線程织堂,完成8個(gè)任務(wù)(最大線程數(shù)是5叠艳,隊(duì)列長(zhǎng)度是3,具體會(huì)在下一篇文章中講)易阳。
可以看到結(jié)果和預(yù)期的一樣
創(chuàng)作不易附较,如果對(duì)你有幫助,歡迎點(diǎn)贊潦俺,收藏和分享啦拒课!