多線程之線程池 · 下
前言
線程池中這塊的內(nèi)容確實要比我預(yù)期的多,當(dāng)然也可能是我講的太細(xì)了,所以比較費字,但是這樣也好,不僅讓各位小伙伴能更清楚相關(guān)邏輯和原理,而且對我而言,讓我可以做到知其然知其所以然,從這個層面上來講,我覺得一切都值得,甚至還有點意外的收獲。
今天是線程池相關(guān)知識點收官之作,今天的內(nèi)容完結(jié)后,基本上線程池這塊的內(nèi)容就可以告一段落了,今天主要從以下幾方面開展:
線程池的其他參數(shù) 線程工廠 拒絕策略處理器
好了,話不多說,讓我們直接開始吧。
線程池
線程工廠
先看第一個參數(shù)——線程工廠,這個參數(shù)的作用是創(chuàng)建線程。在最開始的時候,我們創(chuàng)建線程池的時候并沒有指定這個參數(shù),但是構(gòu)造器內(nèi)部會自動為我們引入默認(rèn)的線程工廠:

下面我們就來看下默認(rèn)的線程工廠是如何實現(xiàn)的:


在線程工廠中,主要有兩部分的操作,第一部分就是設(shè)定創(chuàng)建線程時的信息,包括線程組、線程名稱、堆棧大??;第二部分主要是設(shè)置線程的優(yōu)先級,如果線程是守護(hù)線程的話,會把它修改為非守護(hù)線程(看到這里,我發(fā)現(xiàn)關(guān)于線程組、線程得好好補(bǔ)補(bǔ)課了,后面要跟進(jìn)下)。
我們一般自定義線程工廠主要是為了修改線程名稱,這樣方便我們在排查問題的時候找到我們自己定義的線程池,我們要自頂一下線程工廠只需要沖洗ThreadFactory的newThread方法即可:
static class MyThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
MyThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "syske-" +
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
這里我就直接把默認(rèn)線程工廠的實現(xiàn)copy過來,然后只改了名字,然后在構(gòu)造線程池的時候傳入即可:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new MyThreadFactory());
然后運行下,我們就會發(fā)現(xiàn)線程名稱已經(jīng)變成我們修改之后的了:
當(dāng)然如果只是單純想修改線程池中線程名稱,這樣就太奢侈了,我們可以通過guava提供的ThreadFactoryBuilder直接修改(就是我們之前分享的guava):
ThreadFactory build = new ThreadFactoryBuilder().setNameFormat("syske-task-%d").build();
當(dāng)然,guava還可以設(shè)定其他屬性,甚至可以指定線程工廠,UncaughtExceptionHandler表示未捕獲到的異常處理器:

拒絕策略(飽和策略)
我們在之前的內(nèi)容中說過,如果任務(wù)數(shù)超過corePoolSize + maximumPoolSize + workQueue.size(),線程池就會報拒絕連接的錯誤,這個錯誤就是這里拋出了的,所以接下來我們要分享的就是線程池的最后一個參數(shù)——RejectedExecutionHandler。
和線程工廠一樣,在我們不傳拒絕策略處理器的時候,其實構(gòu)造方法內(nèi)部為我們指定了默認(rèn)的處理器:


默認(rèn)拒絕策略處理器的實現(xiàn)也很簡單:

也可以說是簡單粗暴,直接就拋出了RejectedExecutionException異常。
當(dāng)然我們也可以定義自己的拒絕策略處理器:
static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("線程池拒絕連接,資源已耗盡:r = " + r + ", executor = " + executor);
throw new RejectedExecutionException();
}
}
然后也是在構(gòu)造線程池的時候傳入:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new MyThreadFactory(), new MyRejectedExecutionHandler());
我們把工作對了調(diào)小,把循環(huán)次數(shù)調(diào)大,把休眠時間調(diào)長,然后運行就會觸發(fā)飽和策略,進(jìn)入我們的的rejectedExecution方法:

根據(jù)我的測試,我發(fā)現(xiàn)如果在rejectedExecution方法中直接拋出RejectedExecutionException,會導(dǎo)致主線程進(jìn)入阻塞狀態(tài),這樣整個系統(tǒng)就卡死了

但如果不拋出RejectedExecutionException異常的話,則不會導(dǎo)致阻塞:

所以考慮到實際應(yīng)用情況,我覺得我們還是有必要重寫rejectedExecution方法的,而且我們在方法內(nèi)部還可以做一些業(yè)務(wù)操作,比如線程池擴(kuò)容,或者睡眠等待:

或者可以做一些業(yè)務(wù)告警等操作。
總結(jié)
關(guān)于線程池的創(chuàng)建、參數(shù)以及參數(shù)的作用,經(jīng)過我們這兩天的詳細(xì)分析和演示,我想大家一定也對線程池有了新的認(rèn)知,應(yīng)該說在以后的工作和學(xué)習(xí)中,不論是線程池的使用還是解決線程池相關(guān)的問題,都可以站得更高,看的更遠(yuǎn)。
當(dāng)然,最重要的是,這幾天分享的內(nèi)容都比較實用。明天我們會對線程池這塊做一個小結(jié),然后小結(jié)之外我們會有一些遺漏知識點的補(bǔ)充。好了,今天就先到這里吧!
- END -