線程池的實現原理

線程池的優勢

一、線程是稀缺資源,使用線程池能夠減小建立和銷燬線程的次數,每一個工做線程均可以重複使用。 設置線程的個數請看極客時間上這邊文章的介紹:time.geekbang.org/column/arti…, cpu密集型:cpu核數+1;IO密集型:單核:1 +(I/O 耗時 / CPU 耗時),多核:CPU 核數 * [ 1 +(I/O 耗時 / CPU 耗時)]數組

二、能夠根據系統的承受能力,調整線程池中工做線程的數量,防止由於消耗過多內存致使服務器崩潰。緩存

線程池的建立

public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                RejectedExecutionHandler handler) 
複製代碼
corePoolSize:線程池核心線程數量

maximumPoolSize:線程池最大線程數量

keepAliverTime:當活躍線程數大於核心線程數時,空閒的多餘線程最大存活時間

unit:存活時間的單位

workQueue:存聽任務的隊列

handler:超出線程範圍和隊列容量的任務的處理程序

注:關於workQueue參數的取值,JDK提供了4種阻塞隊列類型供選擇:
            ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
            
            inkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量一般要高於ArrayBlockingQuene 

            SynchronousQuene:一個不存儲元素的阻塞隊列,每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於ArrayBlockingQuene;

            PriorityBlockingQuene:具備優先級的無界阻塞隊列;

     threadFactory:線程工廠,主要用來建立線程;

     handler:表示當拒絕處理任務時的策略,有如下四種取值

 注: 當線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工做線程,若是繼續提交任務,必須採起一種策略處理該任務,線程池提供了4種策略:

        ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。

        ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。

        ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程)

        ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

        固然也能夠根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務。


複製代碼

線程池的實現原理

提交一個任務到線程池中,線程池的處理流程以下:

一、判斷線程池裏的核心線程是否都在執行任務,若是不是(核心線程空閒或者還有核心線程沒有被建立)則建立一個新的工做線程來執行任務。若是核心線程都在執行任務,則進入下個流程。

二、線程池判斷工做隊列是否已滿,若是工做隊列沒有滿,則將新提交的任務存儲在這個工做隊列裏。若是工做隊列滿了,則進入下個流程。

三、判斷線程池裏的線程是否都處於工做狀態,若是沒有,則建立一個新的工做線程來執行任務。若是已經滿了,則交給飽和策略來處理這個任務。
複製代碼

線程池的源碼解讀

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
            //若是線程數大於等於基本線程數或者線程建立失敗,將任務加入隊列
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
           //線程池處於運行狀態而且加入隊列成功
            if (runState == RUNNING && workQueue.offer(command)) {
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command);
            }
           //線程池不處於運行狀態或者加入隊列失敗,則建立線程(建立的是非核心線程)
            else if (!addIfUnderMaximumPoolSize(command))
          //建立線程失敗,則採起阻塞處理的方式
                reject(command); // is shutdown or saturated
        }
    }
複製代碼

初始化四種類型的線程池

一、newFixedThreadPool()

說明:初始化一個指定線程數的線程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene做爲阻塞隊列
特色:即便當線程池沒有可執行任務時,也不會釋放線程。
複製代碼

二、newCachedThreadPool()

說明:初始化一個能夠緩存線程的線程池,默認緩存60s,線程池的線程數可達到Integer.MAX_VALUE,即2147483647,內部使用SynchronousQueue做爲阻塞隊列;
特色:在沒有任務執行時,當線程的空閒時間超過keepAliveTime,會自動釋放線程資源;當提交新任務時,若是沒有空閒線程,則建立新線程執行任務,會致使必定的系統開銷;
所以,使用時要注意控制併發的任務數,防止因建立大量的線程致使而下降性能。
複製代碼

三、newSingleThreadExecutor()

說明:初始化只有一個線程的線程池,內部使用LinkedBlockingQueue做爲阻塞隊列。
特色:若是該線程異常結束,會從新建立一個新的線程繼續執行任務,惟一的線程能夠保證所提交任務的順序執行
複製代碼

四、newScheduledThreadPool()

特定:初始化的線程池能夠在指定的時間內週期性的執行所提交的任務,在實際的業務場景中可使用該線程池按期的同步數據。

總結:除了newScheduledThreadPool的內部實現特殊一點以外,其它線程池內部都是基於ThreadPoolExecutor類(Executor的子類)實現的。
複製代碼

向線程池提交任務

有兩種方式:bash

Executor.execute(Runnable command);

  ExecutorService.submit(Callable<T> task);
複製代碼

execute()的內部實現

1.首次經過workCountof()獲知當前線程池中的線程數,

  若是小於corePoolSize, 就經過addWorker()建立線程並執行該任務;

&emsp;不然,將該任務放入阻塞隊列;

2. 若是能成功將任務放入阻塞隊列中,  

若是當前線程池是非RUNNING狀態,則將該任務從阻塞隊列中移除,而後執行reject()處理該任務;

若是當前線程池處於RUNNING狀態,則須要再次檢查線程池(由於可能在上次檢查後,有線程資源被釋放),是否有空閒的線程;若是有則執行該任務;

三、若是不能將任務放入阻塞隊列中,說明阻塞隊列已滿;那麼將經過addWoker()嘗試建立一個新的線程去執行這個任務;若是addWoker()執行失敗,說明線程池中線程數達到maxPoolSize,則執行reject()處理任務;
複製代碼

sumbit()內部實現

會將提交的Callable任務會被封裝成了一個FutureTask對象

FutureTask類實現了Runnable接口,這樣就能夠經過Executor.execute()提交FutureTask到線程池中等待被執行,最終執行的是FutureTask的run方法; 

比較:

 兩個方法均可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法能夠返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。 
複製代碼

線程生命週期

當線程被建立並啓動後, 並非一啓動就進入執行狀態,也不是一直處於執行狀態.在線程的生命週期中,要通過新建、就緒、運行、阻塞、死亡五種狀態.服務器

新建狀態

當程序使用new關鍵字建立了一個線程以後,該線程就處於新建狀態,此時僅由JVMJ爲其分配內存,並初始化其成員變量的值.併發

就緒狀態

當線程對象調用了 start()方法以後,該線程處於就緒狀態。Java 虛擬機會爲其建立方法調用棧和 程序計數器,等待調度運行。性能

運行狀態

若是處於就緒狀態的線程得到了cpu,開始執行run()方法執行體,則該線程處於運行狀態.ui

阻塞狀態

阻塞狀態是指線程由於某種緣由放棄了cpu使用權,也即讓出了cpu時間片,暫停中止運行.直到線程進入可運行狀態,纔有機會再次得到cpu時間片轉到運行狀態. 線程狀態之間到轉換spa

相關文章
相關標籤/搜索