摘要:所以說(shuō)我們的線程最好是交由線程池來(lái)管理,這樣可以減少對(duì)線程生命周期的管理,一定程度上提高性能。線程池不接收新任務(wù),不處理已添加的任務(wù),并且會(huì)中斷正在處理的任務(wù)。當(dāng)所有的任務(wù)已終止,記錄的任務(wù)數(shù)量為,線程池會(huì)變?yōu)闋顟B(tài)。線程池徹底終止的狀態(tài)。
前言
只有光頭才能變強(qiáng)
回顧前面:
ThreadLocal就是這么簡(jiǎn)單
多線程三分鐘就可以入個(gè)門了!
多線程基礎(chǔ)必要知識(shí)點(diǎn)!看了學(xué)習(xí)多線程事半功倍
Java鎖機(jī)制了解一下
AQS簡(jiǎn)簡(jiǎn)單單過(guò)一遍
Lock鎖子類了解一下
本篇主要是講解線程池,這是我在多線程的倒數(shù)第二篇了,后面還會(huì)有一篇死鎖。主要將多線程的基礎(chǔ)過(guò)一遍,以后有機(jī)會(huì)再繼續(xù)深入!
那么接下來(lái)就開始吧,如果文章有錯(cuò)誤的地方請(qǐng)大家多多包涵,不吝在評(píng)論區(qū)指正哦~
聲明:本文使用JDK1.8一、線程池簡(jiǎn)介
線程池可以看做是線程的集合。在沒(méi)有任務(wù)時(shí)線程處于空閑狀態(tài),當(dāng)請(qǐng)求到來(lái):線程池給這個(gè)請(qǐng)求分配一個(gè)空閑的線程,任務(wù)完成后回到線程池中等待下次任務(wù)(而不是銷毀)。這樣就實(shí)現(xiàn)了線程的重用。
我們來(lái)看看如果沒(méi)有使用線程池的情況是這樣的:
為每個(gè)請(qǐng)求都新開一個(gè)線程!
public class ThreadPerTaskWebServer { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { // 為每個(gè)請(qǐng)求都創(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è)請(qǐng)求都開一個(gè)新的線程雖然理論上是可以的,但是會(huì)有缺點(diǎn):
線程生命周期的開銷非常高。每個(gè)線程都有自己的生命周期,創(chuàng)建和銷毀線程所花費(fèi)的時(shí)間和資源可能比處理客戶端的任務(wù)花費(fèi)的時(shí)間和資源更多,并且還會(huì)有某些空閑線程也會(huì)占用資源。
程序的穩(wěn)定性和健壯性會(huì)下降,每個(gè)請(qǐng)求開一個(gè)線程。如果受到了惡意攻擊或者請(qǐng)求過(guò)多(內(nèi)存不足),程序很容易就奔潰掉了。
所以說(shuō):我們的線程最好是交由線程池來(lái)管理,這樣可以減少對(duì)線程生命周期的管理,一定程度上提高性能。
二、JDK提供的線程池APIJDK給我們提供了Excutor框架來(lái)使用線程池,它是線程池的基礎(chǔ)。
Executor提供了一種將“任務(wù)提交”與“任務(wù)執(zhí)行”分離開來(lái)的機(jī)制(解耦)
下面我們來(lái)看看JDK線程池的總體api架構(gòu):
接下來(lái)我們把這些API都過(guò)一遍看看:
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ù)到池中的情況。
來(lái)源:
https://blog.csdn.net/panweiwei1994/article/details/78969238
2.2補(bǔ)充:Callable和Future學(xué)到了線程池,我們可以很容易地發(fā)現(xiàn):很多的API都有Callable和Future這么兩個(gè)東西。
Future> submit(Runnable task)Future submit(Callable task)
其實(shí)它們也不是什么高深的東西~~~
我們可以簡(jiǎn)單認(rèn)為:Callable就是Runnable的擴(kuò)展。
Runnable沒(méi)有返回值,不能拋出受檢查的異常,而Callable可以!
也就是說(shuō):當(dāng)我們的任務(wù)需要返回值的時(shí),我們就可以使用Callable!
Future一般我們認(rèn)為是Callable的返回值,但他其實(shí)代表的是任務(wù)的生命周期(當(dāng)然了,它是能獲取得到Callable的返回值的)
簡(jiǎn)單來(lái)看一下他們的用法:
public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { // 創(chuàng)建線程池對(duì)象 ExecutorService pool = Executors.newFixedThreadPool(2); // 可以執(zhí)行Runnable對(duì)象或者Callable對(duì)象代表的線程 Futuref1 = pool.submit(new MyCallable(100)); Future 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{ 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)講解它。
我們來(lái)看看頂部注釋:
3.1內(nèi)部狀態(tài)變量ctl定義為AtomicInteger,記錄了“線程池中的任務(wù)數(shù)量”和“線程池的狀態(tài)”兩個(gè)信息。
線程的狀態(tài):
RUNNING:線程池能夠接受新任務(wù),以及對(duì)新添加的任務(wù)進(jìn)行處理。
SHUTDOWN:線程池不可以接受新任務(wù),但是可以對(duì)已添加的任務(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)的處理;可以通過(guò)重載terminated()函數(shù)來(lái)實(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
如果讀懂了上面對(duì)應(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 LinkedBlockingQueue3.2.2newCachedThreadPool()); }
非常有彈性的線程池,對(duì)于新的任務(wù),如果此時(shí)線程池里沒(méi)有空閑線程,線程池會(huì)毫不猶豫的創(chuàng)建一條新的線程去處理這個(gè)任務(wù)。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue3.2.3SingleThreadExecutor()); }
使用單個(gè)worker線程的Executor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue3.3構(gòu)造方法())); }
我們讀完上面的默認(rèn)實(shí)現(xiàn)池還有對(duì)應(yīng)的屬性,再回到構(gòu)造方法看看
構(gòu)造方法可以讓我們自定義(擴(kuò)展)線程池
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, 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ì)象
阻塞隊(duì)列
線程工廠
任務(wù)拒絕策略
再總結(jié)一遍這些參數(shù)的要點(diǎn):
線程數(shù)量要點(diǎn):
如果運(yùn)行線程的數(shù)量少于核心線程數(shù)量,則創(chuàng)建新的線程處理請(qǐng)求
如果運(yùn)行線程的數(shù)量大于核心線程數(shù)量,小于最大線程數(shù)量,則當(dāng)隊(duì)列滿的時(shí)候才創(chuàng)建新的線程
如果核心線程數(shù)量等于最大線程數(shù)量,那么將創(chuàng)建固定大小的連接池
如果設(shè)置了最大線程數(shù)量為無(wú)窮,那么允許線程池適合任意的并發(fā)數(shù)量
線程空閑時(shí)間要點(diǎn):
當(dāng)前線程數(shù)大于核心線程數(shù),如果空閑時(shí)間已經(jīng)超過(guò)了,那該線程會(huì)銷毀。
排隊(duì)策略要點(diǎn):
同步移交:不會(huì)放到隊(duì)列中,而是等待線程執(zhí)行它。如果當(dāng)前線程沒(méi)有執(zhí)行,很可能會(huì)新開一個(gè)線程執(zhí)行。
無(wú)界限策略:如果核心線程都在工作,該線程會(huì)放到隊(duì)列中。所以線程數(shù)不會(huì)超過(guò)核心線程數(shù)
有界限策略:可以避免資源耗盡,但是一定程度上減低了吞吐量
當(dāng)線程關(guān)閉或者線程數(shù)量滿了和隊(duì)列飽和了,就有拒絕任務(wù)的情況了:
拒絕任務(wù)策略:
直接拋出異常
使用調(diào)用者的線程來(lái)處理
直接丟掉這個(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ù)量五、線程池關(guān)閉=corePoolSize,且線程池處于RUNNING狀態(tài),且把提交的任務(wù)成功放入阻塞隊(duì)列中,就再次檢查線程池的狀態(tài), // 1.如果線程池不是RUNNING狀態(tài),且成功從阻塞隊(duì)列中刪除任務(wù),則該任務(wù)由當(dāng)前 RejectedExecutionHandler 處理。 // 2.否則如果線程池中運(yùn)行的線程數(shù)量為0,則通過(guò)addWorker(null, false)嘗試新建一個(gè)線程,新建線程對(duì)應(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不成立,即沒(méi)能將任務(wù)成功放入阻塞隊(duì)列中,且addWoker新建線程失敗,則該任務(wù)由當(dāng)前 RejectedExecutionHandler 處理。 else if (!addWorker(command, false)) reject(command); }
ThreadPoolExecutor提供了shutdown()和shutdownNow()兩個(gè)方法來(lái)關(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ǎn)單地將多線程的結(jié)構(gòu)體系過(guò)了一篇,講了最常用的ThreadPoolExecutor線程池是怎么使用的~~~
明天希望可以把死鎖寫出來(lái),敬請(qǐng)期待~~~
還有剩下的幾個(gè)線程池(給出了參考資料):
ScheduledThreadPoolExecutor
https://blog.csdn.net/panweiwei1994/article/details/78997029
http://cmsblogs.com/?p=2451
ForkJoinPool
https://blog.csdn.net/panweiwei1994/article/details/78992098
參考資料:
《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)航:
https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/69288.html
摘要:此時(shí)線程需要鎖才能繼續(xù)往下執(zhí)行。但是線程的鎖并沒(méi)有釋放,線程的鎖也沒(méi)有釋放。 前言 只有光頭才能變強(qiáng) 回顧前面: ThreadLocal就是這么簡(jiǎn)單 多線程三分鐘就可以入個(gè)門了! 多線程基礎(chǔ)必要知識(shí)點(diǎn)!看了學(xué)習(xí)多線程事半功倍 Java鎖機(jī)制了解一下 AQS簡(jiǎn)簡(jiǎn)單單過(guò)一遍 Lock鎖子類了解一下 線程池你真不來(lái)了解一下嗎? 本篇主要是講解死鎖,這是我在多線程的最后一篇了。主要將多線程...
摘要:受知乎文章和設(shè)計(jì)模式之禪的啟發(fā),我也來(lái)搞一篇腦洞小開的文章由標(biāo)題可知,這篇文章是寫給我女朋友看的。于是這就讓經(jīng)紀(jì)人對(duì)粉絲說(shuō)只有萬(wàn),我才會(huì)寫代碼。 前言 只有光頭才能變強(qiáng) 回顧前面: ThreadLocal就是這么簡(jiǎn)單 多線程三分鐘就可以入個(gè)門了! 多線程基礎(chǔ)必要知識(shí)點(diǎn)!看了學(xué)習(xí)多線程事半功倍 Java鎖機(jī)制了解一下 AQS簡(jiǎn)簡(jiǎn)單單過(guò)一遍 Lock鎖子類了解一下 線程池你真不來(lái)了解一下...
摘要:的方法,的默認(rèn)實(shí)現(xiàn)會(huì)判斷是否是類型注意自動(dòng)拆箱,自動(dòng)裝箱問(wèn)題。適應(yīng)自旋鎖鎖競(jìng)爭(zhēng)是下的,會(huì)經(jīng)過(guò)用戶態(tài)到內(nèi)核態(tài)的切換,是比較花時(shí)間的。在中引入了自適應(yīng)的自旋鎖,說(shuō)明自旋的時(shí)間不固定,要不要自旋變得越來(lái)越聰明。 前言 只有光頭才能變強(qiáng) 之前在刷博客的時(shí)候,發(fā)現(xiàn)一些寫得比較好的博客都會(huì)默默收藏起來(lái)。最近在查閱補(bǔ)漏,有的知識(shí)點(diǎn)比較重要的,但是在之前的博客中還沒(méi)有寫到,于是趁著閑整理一下。 文本的...
摘要:用戶態(tài)不能干擾內(nèi)核態(tài)所以指令就有兩種特權(quán)指令和非特權(quán)指令不同的狀態(tài)對(duì)應(yīng)不同的指令。非特權(quán)指令所有程序均可直接使用。用戶態(tài)常態(tài)目態(tài)執(zhí)行非特權(quán)指令。 這是我今年從三月份開始,主要的大廠面試經(jīng)過(guò),有些企業(yè)面試的還沒(méi)來(lái)得及整理,可能有些沒(méi)有帶答案就發(fā)出來(lái)了,還請(qǐng)各位先思考如果是你怎么回答面試官?這篇文章會(huì)持續(xù)更新,請(qǐng)各位持續(xù)關(guān)注,希望對(duì)你有所幫助! 面試清單 平安產(chǎn)險(xiǎn) 飛豬 上汽大通 浩鯨科...
摘要:有細(xì)心的網(wǎng)友早就想到了這個(gè)問(wèn)題在線程池中,還有一些不常用的設(shè)置。所以該方法會(huì)在線程池總預(yù)先創(chuàng)建沒(méi)有任務(wù)執(zhí)行的線程,數(shù)量為。下面我們測(cè)試一下從測(cè)試結(jié)果來(lái)看,線程池中已經(jīng)預(yù)先創(chuàng)建了數(shù)量的空閑線程。 微信公眾號(hào)「后端進(jìn)階」,專注后端技術(shù)分享:Java、Golang、WEB框架、分布式中間件、服務(wù)治理等等。 老司機(jī)傾囊相授,帶你一路進(jìn)階,來(lái)不及解釋了快上車! 看完我上一篇文章「你都理解創(chuàng)建線...
閱讀 2847·2021-10-11 11:08
閱讀 1555·2021-09-30 09:48
閱讀 1114·2021-09-22 15:29
閱讀 1099·2019-08-30 15:54
閱讀 1058·2019-08-29 15:19
閱讀 588·2019-08-29 13:12
閱讀 3233·2019-08-26 13:53
閱讀 1035·2019-08-26 13:28