Java線程池實現原理

文章目錄

        線程池簡介

        線程池核心類ThreadPoolExecutor

        線程池的工廠類Executors

        線程池的處理流程

        線程池的五種狀態

        線程池提交任務的兩種方式

        線程池的關閉

 

線程池簡介

    多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程和銷燬線程所需要的時間遠遠大於任務執行時間,那麼會大大降低系統的效率。

    因此在Java中可以通過使用線程池來解決這一問題。線程池技術正是關注如何縮短或調整創建線程和銷燬線程所花的時間的技術,從而提高服務器程序性能的。它把創建線程和銷燬線程所花的時間分別安排在服務器程序的啓動和結束的時間段或者一些空閒的時間段,這樣在服務器程序處理客戶請求時,不會有創建線程和銷燬線程所花的時間的開銷了。

    使用線程池的優點:

(1)線程是稀缺資源,使用線程池可以減少創建和銷燬線程的次數,每個工作線程都可以重複使用。

(2)可以根據系統的承受能力,調整線程池中工作線程的數量,防止因爲消耗過多內存導致服務器崩潰。

 

 

線程池核心類ThreadPoolExecutor

     ThreadPoolExecutor類是線程池中最核心的一個類,其它線程池內部都是基於ThreadPoolExecutor類(Executor的子類)實現的。

     在ThreadPoolExecutor類中提供了四個構造方法(其繼承AbstractExecutorService類):

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

各參數說明:

    corePoolSize:核心線程數
    maxPoolSize:最大線程數,它表示在線程池中最多創建多少個線程

(注:ThreadPoolExecutor提供了動態調整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
    keepAliveTime:它表示線程沒有任務執行時最多保持多久時間會終止。

    timeUnit:存活時間的時間單位

(注:在TimeUnit類中有7種靜態屬性:

TimeUnit.DAYS; //天

TimeUnit.HOURS; //小時

TimeUnit.MINUTES; //分鐘

TimeUnit.SECONDS; //秒

TimeUnit.MILLISECONDS; //毫秒

TimeUnit.MICROSECONDS; //微妙

TimeUnit.NANOSECONDS; //納秒

    workQueue:阻塞隊列(用來保存等待被執行的任務)

注:關於workQueue參數的取值,JDK提供了4種阻塞隊列類型供選擇:
ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務;
LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;

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

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

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

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

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

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

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

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

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

  當然也可以根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務。如:RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

 

線程池的工廠類Executors

    我們並不提倡直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個靜態方法來創建線程池,Executors類中提供創建線程池的幾個靜態方法(除了newScheduledThreadPool的內部實現特殊一點之外):

    newFixedThreadPool()
    說明:初始化一個指定線程數的線程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作爲阻塞隊列
    特點:即使當線程池沒有可執行任務時,也不會釋放線程。
    newCachedThreadPool()
    說明:初始化一個可以緩存線程的線程池,默認緩存60s,線程池的線程數可達到Integer.MAX_VALUE,即2147483647,內部使用SynchronousQueue作爲阻塞隊列;
    特點:在沒有任務執行時,當線程的空閒時間超過keepAliveTime,會自動釋放線程資源;當提交新任務時,如果沒有空閒線程,則創建新線程執行任務,會導致一定的系統開銷;
    因此,使用時要注意控制併發的任務數,防止因創建大量的線程導致而降低性能。
    newSingleThreadExecutor()
    說明:初始化只有一個線程的線程池,內部使用LinkedBlockingQueue作爲阻塞隊列。
    特點:如果該線程異常結束,會重新創建一個新的線程繼續執行任務,唯一的線程可以保證所提交任務的順序執行
    newScheduledThreadPool()
    特定:初始化的線程池可以在指定的時間內週期性的執行所提交的任務,在實際的業務場景中可以使用該線程池定期的同步數據。

newCachedThreadPool()內部具體實現:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

 

線程池的處理流程

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

    (2)線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列裏。如果工作隊列滿了,則進入下個流程。

    (3)判斷線程池裏的線程是否都處於工作狀態,如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。

 

線程池的五種狀態

其中AtomicInteger變量ctl的功能非常強大:利用低29位表示線程池中線程數,通過高3位表示線程池的運行狀態:
    RUNNING:-1 << COUNT_BITS,即高3位爲111,該狀態的線程池會接收新任務,並處理阻塞隊列中的任務;
    SHUTDOWN: 0 << COUNT_BITS,即高3位爲000,該狀態的線程池不會接收新任務,但會處理阻塞隊列中的任務;
    STOP : 1 << COUNT_BITS,即高3位爲001,該狀態的線程不會接收新任務,也不會處理阻塞隊列中的任務,而且會中斷正在運行的任務;
    TIDYING : 2 << COUNT_BITS,即高3位爲010,該狀態表示線程池對線程進行整理優化;
    TERMINATED: 3 << COUNT_BITS,即高3位爲011,該狀態表示線程池停止工作;

 

線程池提交任務的兩種方式

有兩種方式:

      Executor.execute(Runnable command);

      ExecutorService.submit(Callable<T> task);

    在ThreadPoolExecutor類中,最核心的任務提交方法是execute()方法,雖然通過submit也可以提交任務,但是實際上submit方法裏面最終調用的還是execute()方法,FutureTask類實現了Runnable接口,其會將提交的Callable任務會被封裝成了一個FutureTask對象。

    execute()方法的內部實現:

public void execute(Runnable command) {

    if (command == null)
        throw new NullPointerException();//拋出空指針異常
    //判斷當前線程數是否大於等於核心線程池大小和addIfUnderCorePoolSize()返回值
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        //判斷當前線程池狀態是否爲RUNNING,當前線程池處於RUNNING狀態且將任務放入任務緩存隊列成功
        if (runState == RUNNING && workQueue.offer(command)) {
        //這句判斷是爲了防止在將此任務添加進任務緩存隊列的同時其他線程突然調用shutdown或者shutdownNow方法關閉了線程池的一種應急措施。
            if (runState != RUNNING || poolSize == 0)

                ensureQueuedTaskHandled(command);

        }
        //如果執行addIfUnderMaximumPoolSize方法失敗,則執行reject()方法進行任務拒絕處理。
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated

    }

}
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < corePoolSize && runState == RUNNING)
            t = addThread(firstTask);        //創建線程去執行firstTask任務   
        } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < maximumPoolSize && runState == RUNNING)
            t = addThread(firstTask);
    } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

 

線程池的關閉

ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:

    shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務

    shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務

 

 

參考資料:

https://www.cnblogs.com/zhaojinxin/p/6668247.html

https://blog.csdn.net/honghailiang888/article/details/51690711

https://blog.csdn.net/gol_phing/article/details/49032055

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章