一、線程池的好處

線程池是啥子,幹啥使它呀,老子線程使得好好的,非得多次一舉,哈哈,想必來這裏看這篇文章的都對線程池有點瞭解。那麼我來整理整理線程池的好處吧。


線程池的好處,詳解,單例(絕對好記)


1、線程池的重用

線程的創建和銷燬的開銷是巨大的,而通過線程池的重用大大減少了這些不必要的開銷,當然既然少了這麼多消費內存的開銷,其線程執行速度也是突飛猛進的提升。

2、控制線程池的併發數

初學新手可能對併發這個詞語比較陌生,特此我也是結合百度百科和必生所學得出最優解釋,萬萬記着併發可跟並行不一樣。

併發:在某個時間段內,多個程序都處在執行和執行完畢之間;但在一個時間點上只有一個程序在運行。頭腦風暴:老鷹媽媽喂小雛鷹食物,小雛鷹很多,而老鷹只有一張嘴,她需要一個個餵過去,到最後每個小雛鷹都可以喫到,但是在一個時間點裏只能有一個小雛鷹可以喫到美味的食物。

並行:在某個時間段裏,每個程序按照自己獨立異步的速度執行,程序之間互不幹擾。頭腦風暴:這就好似老鷹媽媽決定這樣餵食太費勁於是爲每個小雛鷹請了個保姆,這樣子在一個時間點裏,每個小雛鷹都可以同時喫到食物,而且互相不幹擾。

回到線程池,控制線程池的併發數可以有效的避免大量的線程池爭奪CPU資源而造成堵塞。頭腦風暴:還是拿老鷹的例子來講,媽媽只有一個,要這麼一個個喂下去,一些餓壞的小雛鷹等不下去了就要破壞規則,搶在靠前餵食的雛鷹面前,而前面的雛鷹也不是喫軟飯的,於是打起來了,場面混亂。老鷹生氣了,這麼不懂事,誰也別喫了,於是造成了最後誰也沒食喫的局面。

3、線程池可以對線程進行管理

線程池可以提供定時、定期、單線程、併發數控制等功能。比如通過ScheduledThreadPool線程池來執行S秒後,每隔N秒執行一次的任務。

二、線程池的詳解

想必看完上面那篇博客,大家可謂讚不絕口,不過可能有些小夥伴還是記不下來,還有些小夥伴覺得好惡心呀,怎麼都是廁所啥的呀!哈哈彆着急,我來給大家一種好記的辦法。

先來講講參數最多的那個構造方法,主要是對那幾個煩人的參數進行分析。

1、ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize, 
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)


這裏是7個參數(我們在開發中用的更多的是5個參數的構造方法),OK,那我們來看看這裏七個參數的含義:

  • corePoolSize:線程池中核心線程的數量。
  • maximumPoolSize:線程池中最大線程數量。
  • keepAliveTime:非核心線程的超時時長,當系統中非核心線程閒置時間超過keepAliveTime之後,則會被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true,則該參數也表示核心線程的超時時長。
  • unit:第三個參數的單位,有納秒、微秒、毫秒、秒、分、時、天等。
  • workQueue:線程池中的任務隊列,該隊列主要用來存儲已經被提交但是尚未執行的任務。存儲在這裏的任務是由ThreadPoolExecutor的execute方法提交來的。
  • threadFactory:爲線程池提供創建新線程的功能,這個我們一般使用默認即可。
  • handler:拒絕策略,當線程無法執行新任務時(一般是由於線程池中的線程數量已經達到最大數或者線程池關閉導致的),默認情況下,當線程池無法處理新線程時,會拋出一個RejectedExecutionException。

emmmmm....看到那麼多煩人的概念,是不是有點頭大了,我反正是頭大了。

這7個參數中,平常最多用到的是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue.在這裏我主抽出corePoolSize、maximumPoolSize和workQueue三個參數進行詳解。

maximumPoolSize(最大線程數) = corePoolSize(核心線程數) + noCorePoolSize(非核心線程數);

(1)當currentSize

(2)當currentSize>=corePoolSize、並且workQueue未滿時,添加進來的任務會被安排到workQueue中等待執行。

(3)當workQueue已滿,但是currentSize

(4)當currentSize>=corePoolSize、workQueue已滿、並且currentSize>maximumPoolSize時,調用handler默認拋出RejectExecutionExpection異常。

什麼currentSize,corePoolSize,maximumPoolSize,workQueue比來比去的都比迷糊了,哈哈,那我舉個燒烤店的例子來想必大家理解起來更快。

夏天了,很熱,所以很多燒烤店都會在外面也佈置座位,分爲室內、室外兩個地方可以喫燒烤。(室內有空調電視,而且室內比室外燒烤更加優惠,而且外面下着瓢潑大雨所以顧客會首先選擇室內)

corePoolSize(燒烤店室內座位),cuurentPoolSize(目前到燒烤店的顧客數量),maximumPoolSize(燒烤店室內+室外+侯廳室所有座位),workQueue(燒烤店爲顧客專門設置的侯廳室)

第(1)種,燒烤店人數不多的時候,室內位置很多,大家都其樂融融,開心的坐在室內喫着燒烤,看着世界盃。

第(2)種,生意不錯,室內燒烤店坐無空席,大家都不願意去外面喫,於是在侯廳室裏待著,侯廳室位置沒坐滿。

第(3)種,生意興隆,室內、侯廳室都坐無空席,但是顧客太餓了,剩下的人沒辦法只好淋着雨喫燒烤,哈哈,好可憐。

第(4)種,生意爆棚,室內、室外、侯廳室都坐無空席,在有顧客過來直接趕走。

哈哈是不是很形象,對於workQueue還是有點陌生的小夥伴。

2、其他線程池的記法

剩下的那四種主要的線程池大概思路,用法在我推薦的博客裏都有詳細解說,在這裏我就不一一道來了,在這裏主要是跟大家分享一種特別容易記住這四種線程池的方法,在大家寫代碼,面試時可以即使想到這四種線程池。

(1)FixedThreadPool:

Fixed中文解釋爲固定。結合在一起解釋固定的線程池,說的更全面點就是,有固定數量線程的線程池。其corePoolSize=maximumPoolSize,且keepAliveTime爲0,適合線程穩定的場所。

(2)SingleThreadPool:

Single中文解釋爲單一。結合在一起解釋單一的線程池,說的更全面點就是,有固定數量線程的線程池,且數量爲一,從數學的角度來看SingleThreadPool應該屬於FixedThreadPool的子集。其corePoolSize=maximumPoolSize=1,且keepAliveTime爲0,適合線程同步操作的場所。

(3)CachedThreadPool:

Cached中文解釋爲儲存。結合在一起解釋儲存的線程池,說的更通俗易懂,既然要儲存,其容量肯定是很大,所以他的corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE(2^32-1一個很大的數字)

(4)ScheduledThreadPool:

Scheduled中文解釋爲計劃。結合在一起解釋計劃的線程池,顧名思義既然涉及到計劃,必然會涉及到時間。所以ScheduledThreadPool是一個具有定時定期執行任務功能的線程池。

三、線程池的單例

容我伸個懶腰,該講本章重點內容了,在此之前,我們對基本語意知識進行了解一下。

什麼是單例呢?咳咳。

1、單例

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

注意事項:

  1. 單例類只能有一個實例。
  2. 單例類必須自己創建自己的唯一實例。
  3. 單例類必須給所有其他對象提供這一實例。

2、線程池的單例

那麼問題來了,我線程池用的好好的,用的時候創建一個,不用就不管他,那爲什麼要將線程池設計成單例模式呢。那麼就要看看你將線程池應用的場所了。一般情況下,整個系統中只需要單種線程池,多個線程公用一個線程池,不會是每創一個線程就要創建一個線程池,那樣子你還不如不用線程池呢。

言歸正傳,咱們來看看如何將線程池設計成單例模式。廢話少說上代碼首先在ThreadPool類裏面實現線程池的創建,我們這裏創建的是FixedThreadPool線程池(記住構造方法要私有,保證不被其他類實例化)
private ThreadPool(int corepoolsize, int maximumpoolsize, long keepalivetime){
this.corepoolsize = corepoolsize;
this.maximumpoolsize = maximumpoolsize;
this.keepalivetime = keepalivetime;
}
public void executor(Runnable runnable){
if (runnable == null){
return;
}
if (mexecutor == null){
mexecutor = new ThreadPoolExecutor(corepoolsize, //核心線程數
maximumpoolsize, //最大線程數
keepalivetime, //閒置線程存活時間
TimeUnit.MILLISECONDS, // 時間單位
new LinkedBlockingDeque(), //線程隊列
Executors.defaultThreadFactory(), //線程工廠
new ThreadPoolExecutor.AbortPolicy() //隊列已滿,而且當前線程數已經超過最大線程數時的異常處理策略
);
}
mexecutor.execute(runnable);
}


再然後對ThreadPool內部類,在類裏面對他實例化,實現單例

// 獲取單例的線程池對象
public static ThreadPool getThreadPool() {
if (mThreadPool == null) {
synchronized (ThreadManager.class) {
if (mThreadPool == null) {
int cpuNum = Runtime.getRuntime().availableProcessors();// 獲取處理器數量
int threadNum = cpuNum * 2 + 1;// 根據cpu數量,計算出合理的線程併發數
mThreadPool = new ThreadPool(threadNum, threadNum, 0L);
}
}
}
return mThreadPool;
}


菜鳥一隻,如有不對之處請指出。您的鼓勵是我寫作的最大動力!

擴展閱讀

Java併發編程:線程池的使用

java高級應用:線程池全面解析

Java 線程池(ThreadPoolExecutor)原理分析與使用

來源:https://blog.csdn.net/fengye454545/article/details/79536986

相關文章