詳談執行緒池的理解和應用

巔峰大詞典發表於2020-08-10

正由於我抱著與你相見的希望,我才永遠認為最崎嶇的路是最好的路。

一、執行緒池的好處

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

1、執行緒池的重用

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

2、控制執行緒池的併發數

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

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

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

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

3、執行緒池可以對執行緒進行管理

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

二、執行緒池的詳解

推薦部落格:http://blog.csdn.net/seu_calvin/article/details/52415337

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

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

1、ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> 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<corePoolSize時,沒什麼好說的,直接啟動一個核心執行緒並執行任務。

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

(3)當workQueue已滿,但是currentSize<maximumPoolSize時,會立即開啟一個非核心執行緒來執行任務。

(4)當currentSize>=corePoolSize、workQueue已滿、並且currentSize>maximumPoolSize時,呼叫handler預設丟擲RejectExecutionExpection異常。

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

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

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

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

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

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

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

對於workQueue還是有點陌生的小夥伴。

推薦部落格:http://blog.csdn.net/u0127025...

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 中最簡單的設計模式之一。這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。

注意事項:

  • 單例類只能有一個例項。
  • 單例類必須自己建立自己的唯一例項。
  • 單例類必須給所有其他物件提供這一例項。
推薦:http://www.runoob.com/design-...

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<Runnable>(), //執行緒佇列
                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;
}

相關文章