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