前言
只有光頭才能變強(qiáng)
回顧前面:
- ThreadLocal就是這么簡單
- 多線程三分鐘就可以入個(gè)門了!
- 多線程基礎(chǔ)必要知識(shí)點(diǎn)窟感!看了學(xué)習(xí)多線程事半功倍
- Java鎖機(jī)制了解一下
- AQS簡簡單單過一遍
- Lock鎖子類了解一下
本篇主要是講解線程池讨彼,這是我在多線程的倒數(shù)第二篇了,后面還會(huì)有一篇死鎖柿祈。主要將多線程的基礎(chǔ)過一遍哈误,以后有機(jī)會(huì)再繼續(xù)深入哩至!
那么接下來就開始吧,如果文章有錯(cuò)誤的地方請大家多多包涵蜜自,不吝在評(píng)論區(qū)指正哦~
聲明:本文使用JDK1.8
一菩貌、線程池簡介
線程池可以看做是線程的集合。在沒有任務(wù)時(shí)線程處于空閑狀態(tài)重荠,當(dāng)請求到來:線程池給這個(gè)請求分配一個(gè)空閑的線程箭阶,任務(wù)完成后回到線程池中等待下次任務(wù)(而不是銷毀)。這樣就實(shí)現(xiàn)了線程的重用戈鲁。
我們來看看如果沒有使用線程池的情況是這樣的:
- 為每個(gè)請求都新開一個(gè)線程仇参!
public class ThreadPerTaskWebServer {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
// 為每個(gè)請求都創(chuàng)建一個(gè)新的線程
final Socket connection = socket.accept();
Runnable task = () -> handleRequest(connection);
new Thread(task).start();
}
}
private static void handleRequest(Socket connection) {
// request-handling logic here
}
}
為每個(gè)請求都開一個(gè)新的線程雖然理論上是可以的,但是會(huì)有缺點(diǎn):
- 線程生命周期的開銷非常高婆殿。每個(gè)線程都有自己的生命周期诈乒,創(chuàng)建和銷毀線程所花費(fèi)的時(shí)間和資源可能比處理客戶端的任務(wù)花費(fèi)的時(shí)間和資源更多,并且還會(huì)有某些空閑線程也會(huì)占用資源婆芦。
- 程序的穩(wěn)定性和健壯性會(huì)下降抓谴,每個(gè)請求開一個(gè)線程。如果受到了惡意攻擊或者請求過多(內(nèi)存不足)寞缝,程序很容易就奔潰掉了癌压。
所以說:我們的線程最好是交由線程池來管理,這樣可以減少對線程生命周期的管理荆陆,一定程度上提高性能滩届。
二、JDK提供的線程池API
JDK給我們提供了Excutor框架來使用線程池被啼,它是線程池的基礎(chǔ)帜消。
- Executor提供了一種將“任務(wù)提交”與“任務(wù)執(zhí)行”分離開來的機(jī)制(解耦)
下面我們來看看JDK線程池的總體api架構(gòu):
接下來我們把這些API都過一遍看看:
Executor接口:
ExcutorService接口:
AbstractExecutorService類:
ScheduledExecutorService接口:
ThreadPoolExecutor類:
ScheduledThreadPoolExecutor類:
2.1ForkJoinPool線程池
除了ScheduledThreadPoolExecutor和ThreadPoolExecutor類線程池以外,還有一個(gè)是JDK1.7新增的線程池:ForkJoinPool線程池
于是我們的類圖就可以變得完整一些:
JDK1.7中新增的一個(gè)線程池浓体,與ThreadPoolExecutor一樣泡挺,同樣繼承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的兩大核心類之一命浴。與其它類型的ExecutorService相比娄猫,其主要的不同在于采用了工作竊取算法(work-stealing):所有池中線程會(huì)嘗試找到并執(zhí)行已被提交到池中的或由其他線程創(chuàng)建的任務(wù)。這樣很少有線程會(huì)處于空閑狀態(tài)生闲,非常高效媳溺。這使得能夠有效地處理以下情景:大多數(shù)由任務(wù)產(chǎn)生大量子任務(wù)的情況;從外部客戶端大量提交小任務(wù)到池中的情況碍讯。
來源:
2.2補(bǔ)充:Callable和Future
學(xué)到了線程池悬蔽,我們可以很容易地發(fā)現(xiàn):很多的API都有Callable和Future這么兩個(gè)東西。
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
其實(shí)它們也不是什么高深的東西~~~
我們可以簡單認(rèn)為:Callable就是Runnable的擴(kuò)展捉兴。
- Runnable沒有返回值蝎困,不能拋出受檢查的異常录语,而Callable可以!
也就是說:當(dāng)我們的任務(wù)需要返回值的時(shí)禾乘,我們就可以使用Callable澎埠!
Future一般我們認(rèn)為是Callable的返回值,但他其實(shí)代表的是任務(wù)的生命周期(當(dāng)然了盖袭,它是能獲取得到Callable的返回值的)
簡單來看一下他們的用法:
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創(chuàng)建線程池對象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執(zhí)行Runnable對象或者Callable對象代表的線程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 結(jié)束
pool.shutdown();
}
}
Callable任務(wù):
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
執(zhí)行完任務(wù)之后可以獲取得到任務(wù)返回的數(shù)據(jù):
三失暂、ThreadPoolExecutor詳解
這是用得最多的線程池彼宠,所以本文會(huì)重點(diǎn)講解它鳄虱。
我們來看看頂部注釋:
3.1內(nèi)部狀態(tài)
變量ctl定義為AtomicInteger,記錄了“線程池中的任務(wù)數(shù)量”和“線程池的狀態(tài)”兩個(gè)信息凭峡。
線程的狀態(tài):
- RUNNING:線程池能夠接受新任務(wù)拙已,以及對新添加的任務(wù)進(jìn)行處理。
- SHUTDOWN:線程池不可以接受新任務(wù)摧冀,但是可以對已添加的任務(wù)進(jìn)行處理倍踪。
- STOP:線程池不接收新任務(wù),不處理已添加的任務(wù)索昂,并且會(huì)中斷正在處理的任務(wù)建车。
- TIDYING:當(dāng)所有的任務(wù)已終止,ctl記錄的"任務(wù)數(shù)量"為0椒惨,線程池會(huì)變?yōu)門IDYING狀態(tài)缤至。當(dāng)線程池變?yōu)門IDYING狀態(tài)時(shí),會(huì)執(zhí)行鉤子函數(shù)terminated()康谆。terminated()在ThreadPoolExecutor類中是空的领斥,若用戶想在線程池變?yōu)門IDYING時(shí),進(jìn)行相應(yīng)的處理沃暗;可以通過重載terminated()函數(shù)來實(shí)現(xiàn)月洛。
- TERMINATED:線程池徹底終止的狀態(tài)。
各個(gè)狀態(tài)之間轉(zhuǎn)換:
3.2已默認(rèn)實(shí)現(xiàn)的池
下面我就列舉三個(gè)比較常見的實(shí)現(xiàn)池:
- newFixedThreadPool
- newCachedThreadPool
- SingleThreadExecutor
如果讀懂了上面對應(yīng)的策略呀孽锥,線程數(shù)量這些嚼黔,應(yīng)該就不會(huì)太難看懂了。
3.2.1newFixedThreadPool
一個(gè)固定線程數(shù)的線程池惜辑,它將返回一個(gè)corePoolSize和maximumPoolSize相等的線程池隔崎。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3.2.2newCachedThreadPool
非常有彈性的線程池,對于新的任務(wù)韵丑,如果此時(shí)線程池里沒有空閑線程爵卒,線程池會(huì)毫不猶豫的創(chuàng)建一條新的線程去處理這個(gè)任務(wù)。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3.2.3SingleThreadExecutor
使用單個(gè)worker線程的Executor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
3.3構(gòu)造方法
我們讀完上面的默認(rèn)實(shí)現(xiàn)池還有對應(yīng)的屬性撵彻,再回到構(gòu)造方法看看
- 構(gòu)造方法可以讓我們自定義(擴(kuò)展)線程池
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;
}
- 指定核心線程數(shù)量
- 指定最大線程數(shù)量
- 允許線程空閑時(shí)間
- 時(shí)間對象
- 阻塞隊(duì)列
- 線程工廠
- 任務(wù)拒絕策略
再總結(jié)一遍這些參數(shù)的要點(diǎn):
線程數(shù)量要點(diǎn):
- 如果運(yùn)行線程的數(shù)量少于核心線程數(shù)量钓株,則創(chuàng)建新的線程處理請求
- 如果運(yùn)行線程的數(shù)量大于核心線程數(shù)量实牡,小于最大線程數(shù)量,則當(dāng)隊(duì)列滿的時(shí)候才創(chuàng)建新的線程
- 如果核心線程數(shù)量等于最大線程數(shù)量轴合,那么將創(chuàng)建固定大小的連接池
- 如果設(shè)置了最大線程數(shù)量為無窮创坞,那么允許線程池適合任意的并發(fā)數(shù)量
線程空閑時(shí)間要點(diǎn):
- 當(dāng)前線程數(shù)大于核心線程數(shù),如果空閑時(shí)間已經(jīng)超過了受葛,那該線程會(huì)銷毀题涨。
排隊(duì)策略要點(diǎn):
- 同步移交:不會(huì)放到隊(duì)列中,而是等待線程執(zhí)行它总滩。如果當(dāng)前線程沒有執(zhí)行纲堵,很可能會(huì)新開一個(gè)線程執(zhí)行。
- 無界限策略:如果核心線程都在工作闰渔,該線程會(huì)放到隊(duì)列中席函。所以線程數(shù)不會(huì)超過核心線程數(shù)
- 有界限策略:可以避免資源耗盡,但是一定程度上減低了吞吐量
當(dāng)線程關(guān)閉或者線程數(shù)量滿了和隊(duì)列飽和了冈涧,就有拒絕任務(wù)的情況了:
拒絕任務(wù)策略:
- 直接拋出異常
- 使用調(diào)用者的線程來處理
- 直接丟掉這個(gè)任務(wù)
- 丟掉最老的任務(wù)
四茂附、execute執(zhí)行方法
execute執(zhí)行方法分了三步,以注釋的方式寫在代碼上了~
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果線程池中運(yùn)行的線程數(shù)量<corePoolSize督弓,則創(chuàng)建新線程來處理請求营曼,即使其他輔助線程是空閑的。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果線程池中運(yùn)行的線程數(shù)量>=corePoolSize愚隧,且線程池處于RUNNING狀態(tài)蒂阱,且把提交的任務(wù)成功放入阻塞隊(duì)列中,就再次檢查線程池的狀態(tài)奸攻,
// 1.如果線程池不是RUNNING狀態(tài)蒜危,且成功從阻塞隊(duì)列中刪除任務(wù),則該任務(wù)由當(dāng)前 RejectedExecutionHandler 處理睹耐。
// 2.否則如果線程池中運(yùn)行的線程數(shù)量為0辐赞,則通過addWorker(null, false)嘗試新建一個(gè)線程,新建線程對應(yīng)的任務(wù)為null硝训。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果以上兩種case不成立响委,即沒能將任務(wù)成功放入阻塞隊(duì)列中,且addWoker新建線程失敗窖梁,則該任務(wù)由當(dāng)前 RejectedExecutionHandler 處理赘风。
else if (!addWorker(command, false))
reject(command);
}
五、線程池關(guān)閉
ThreadPoolExecutor提供了shutdown()
和shutdownNow()
兩個(gè)方法來關(guān)閉線程池
shutdown() :
shutdownNow():
區(qū)別:
- 調(diào)用shutdown()后纵刘,線程池狀態(tài)立刻變?yōu)镾HUTDOWN邀窃,而調(diào)用shutdownNow(),線程池狀態(tài)立刻變?yōu)镾TOP假哎。
- shutdown()等待任務(wù)執(zhí)行完才中斷線程瞬捕,而shutdownNow()不等任務(wù)執(zhí)行完就中斷了線程鞍历。
六、總結(jié)
本篇博文主要簡單地將多線程的結(jié)構(gòu)體系過了一篇肪虎,講了最常用的ThreadPoolExecutor線程池是怎么使用的~~~
明天希望可以把死鎖寫出來劣砍,敬請期待~~~
還有剩下的幾個(gè)線程池(給出了參考資料):
- ScheduledThreadPoolExecutor
- ForkJoinPool
參考資料:
- 《Java核心技術(shù)卷一》
- 《Java并發(fā)編程實(shí)戰(zhàn)》
- http://cmsblogs.com/?page_id=111
- https://blog.csdn.net/panweiwei1994/article/details/78483167
- https://zhuanlan.zhihu.com/p/35382932
如果文章有錯(cuò)的地方歡迎指正,大家互相交流扇救。習(xí)慣在微信看技術(shù)文章刑枝,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號(hào):Java3y迅腔。為了大家方便装畅,剛新建了一下qq群:742919422,大家也可以去交流交流钾挟。謝謝支持了洁灵!希望能多介紹給其他有需要的朋友
文章的目錄導(dǎo)航: