1. 程式人生 > >Java併發系列筆記之執行緒池

Java併發系列筆記之執行緒池

執行緒池是資源複用的典範之作,其基本思想是維護一個含有一定數量的在執行的執行緒集合,在需要執行執行緒任務的時候直接從這個集合中取出一個執行緒去執行任務,而不是重新建立一個。

如果我們自己去實現一個執行緒池,那麼基本的想法是維護一個執行緒的集合,這些執行緒都從一個佇列中去取任務,如果佇列為空,則阻塞對應的執行緒,等待佇列不空的訊息通知。當執行緒完成了任務,應該將執行緒返回給執行緒佇列,而不是關閉執行緒。基本思想是這樣,那麼Java中具體是如何實現執行緒池的呢,我們來看看。

ThreadPoolExecutor

一般情況下,我們使用執行緒池會使用Executors的靜態方法獲取執行緒池,如
* public static ExecutorService newFixedThreadPool(int nThreads)
* public static ExecutorService newSingleThreadExecutor()
* public static ExecutorService newCachedThreadPool()
* public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

除了最後一個方法外,其它三個方法都是返回一個ThreadPoolExecutor例項,它是ExecutorService介面的一個實現類。瞄一眼ThreadPoolExecutor的構造方法

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

它的構造方法有多個過載,其中最終都是使用了上面這個構造方法,它包括7個引數,corePoolSize,核心執行緒數量;maximumPoolSize,最大執行緒數量;keepAliveTime,超過核心執行緒數量執行緒的在空閒後的存活時間;unit,存活時間的時間單位;workQueue,超過核心執行緒後任務存放的阻塞佇列;threadFactory,常用定義執行緒名字的執行緒工廠,可以使用預設工廠;最後一個handler,是阻塞佇列已滿,並且執行緒數達到maximumPoolSize的時候的處理策略,其中包括了丟擲異常(AbortPolicy),誰請求誰呼叫(CallerRunsPolicy),丟棄執行緒池中的一個任務來執行現在的任務(DiscardOldesPolicy),直接丟棄掉(DiscardPolicy)預設使用拋異常策略。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可以看到newFixedThreadPool使用預設工廠,預設拒絕策略,連結串列阻塞佇列,最大執行緒數和核心執行緒數相同,並且如果超過核心執行緒數的執行緒控制,立即失效。這種比較適合任務數固定的任務。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

newSingleThreadExecutor的核心執行緒數和最大執行緒數都是1,並且使用LinkedBlockingQueue作為緩衝佇列。

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

newCachedThreadPool在沒有任務的時候並不建立執行緒,只有在任務出現的時候才使用SynchronousQueue佇列傳遞任務給執行緒,但SynchronousQueue每次只能傳遞一個任務,新的任務首先會通過offer(非阻塞)方法嘗試傳遞任務給執行緒,如果此時沒有空閒的執行緒,會新生成執行緒來完成任務,而且執行緒的空閒時間是60s,所以比較適合任務多而且短的任務集(短的原因是總可以有空閒執行緒去SynchronousQueue中取任務)。關於SynchronousQueue,可以參考這幾篇文章《SynchronousQueue使用例項》,《J.U.C之阻塞佇列:SynchronousQueue》。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

ScheduledThreadPoolExecutor可以使用schedule方法在給定時間延遲時進行呼叫。

ThreadPoolExecutor是如何管理執行緒池的?

從上面我們看到使用ThreadPoolExecutor可以獲取執行緒池,並且可以呼叫execute(Runnable)和submit(Runnable)提交任務。那麼在執行execute方法的時候,ThreadPoolExecutor是如何管理執行緒池來完成當前的任務的。我們可以從execute的原始碼中一窺這個處理策略

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {//第一步
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        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);
        }
        else if (!addWorker(command, false))//第三步
            reject(command);
    }

其基本策略有三大步,第一步,在當前的執行緒池中執行緒數量小於核心執行緒數量,直接呼叫addWork(Runnable,boolean)新增執行緒,並且執行任務任務,任務完成後這個執行緒會駐留在池中等待執行任務。如果執行緒數已經超過的核心執行緒數,第二步,則將任務使用阻塞佇列的offer非阻塞方式放入佇列中。如果阻塞佇列也滿了,則嘗試addWork(Runnable,boolean),如果執行緒數量超過了最大執行緒數量,則新增失敗,呼叫拒絕策略。
addWork(Runnable)方法基本思想是首先將Runable包裝成Worker,並執行Worker。Worker的資料結構使得在執行完該方法後,會嘗試從阻塞佇列中獲取任務繼續執行。
另外值得注意的是JDK中的執行緒池標註的5種狀態,如下圖所示

只有RUNNING狀態的執行緒池可以接收任務,處於SHUTDOWN狀態的執行緒池不可以接受新任務,但是可以繼續對已新增的任務進行處理。處於STOP狀態的執行緒池不接收新任務,不處理已新增的任務,並且會中斷正在處理的任務。TIDYING狀態的執行緒池在執行terminated函式,而TERMINATED狀態說明已經執行完terminated函式,徹底終止。其中RUNNING可以轉換成SHUTDOWN(shutdown()函式)或STOP狀態(shutDownNow()函式),有STOP或SHUTDOWN狀態可以轉換成TIDYING,而TIDYING可以轉換成TERMINATED。

參考文獻

宣告

本文首發表於我的部落格,歡迎關注!轉載須註明文章出處,作者保留文章所有權。

相關推薦

Java併發系列筆記執行

執行緒池是資源複用的典範之作,其基本思想是維護一個含有一定數量的在執行的執行緒集合,在需要執行執行緒任務的時候直接從這個集合中取出一個執行緒去執行任務,而不是重新建立一個。 如果我們自己去實現一個執行緒池,那麼基本的想法是維護一個執行緒的集合,這些執行緒都

Java併發程式設計筆記4-執行

我們使用執行緒的時候就去建立一個執行緒,但是就會有一個問題:  如果併發的執行緒數量非常多,而且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會導致大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。那麼有沒有一種辦法使得執行緒可以複用,就是執行完

菜鳥路——Java併發(四)執行的使用

  轉自:http://www.cnblogs.com/dolphin0520/p/3932921.html   在前面的文章中,我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便,但是就會有一個問題:   如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間

【架構】Java併發程式設計 - 深入剖析執行

前言 如果我們要使用執行緒的時候就去建立一個,這樣雖然非常簡便,但是就會有一個問題: 如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。 那麼有沒有一種辦法使得執行緒可以複用

Java 併發程式設計深入學習——執行及其實現原理

Java執行緒池介紹   執行緒池,從字面含義來看,是指管理一組同構工作執行緒的資源池。執行緒池是與工作佇列(work Queue)密切相關的,其中工作佇列中儲存了所有等待執行的任務。工作者執行緒(Work Thread)的任務很簡單:從工作佇列中獲取一個任務,執行任務,然

Java 併發:Executors 和執行

讓我們開始來從入門瞭解一下 Java 的併發程式設計。 本文主要介紹如何開始建立執行緒以及管理執行緒池,在 Java 語言中,一個最簡單的執行緒如下程式碼所示: Runnable runnable = new Runnable(){ public v

盤點java併發包提供的執行和佇列

執行緒池 newCachedThreadPool() newFixedThreadPool(int nThreads) newSingleThreadPoolExecutor() newScheduledThreadPool(int corePoolSize

Java併發(十)執行&fork/join框架

什麼是執行緒池 第四種獲取執行緒的方法:執行緒池,一個 ExecutorService,它使用可能的幾個池執行緒之一執行每個提交的任務,通常使用 Executors 工廠方法配置。 執行緒池可以解決兩個不同問題:由於減少了每個任務呼叫的開銷,它們通常可以在執

Java併發程式設計實戰:執行的應用

一、任務與執行策略間的隱形耦合 1、執行緒飢餓死鎖 當任務都是同類、獨立的時候,執行緒池才會有最好的工作表現。如果將耗時的短期的任務混合在一起,除非執行緒池很大,否則會有“塞車”的風險;如果提交的任務要依賴其他的任務,除非池是無限的,否則有產生死鎖的風險。如下程式碼所示,對

Java併發程式設計--CountDownLatch配合執行

Java併發程式設計–CountDownLatch配合執行緒池 在處理一些耗時操作的時候,我們習慣上會把它放在子執行緒裡面做,是的如果允許(我是指操作的內容),為了更快速地完成這堆操作,執行緒池是一個不錯的選擇。等等,好像有點不對,如果需要等待所有的執行緒在執

Java併發讀書筆記執行安全與互斥同步

目錄 導致執行緒不安全的原因 什麼是執行緒安全 不可變 絕對執行緒安全 相對執行緒安全 執行緒相容 執行緒對立 互斥同步實現執

java併發包學習系列執行複用執行

什麼是執行緒池 頻繁使用new Thread來建立執行緒的方式並不太好。因為每次new Thread新建和銷燬物件效能較差,執行緒缺乏統一管理。好在Java提供了執行緒池,它能夠有效的管理、排程執行緒,避免過多的資源消耗。優點如下: 重用存在的執行緒,減少物

JAVA學習筆記併發程式設計 - 捌)- 執行

文章目錄 執行緒池 執行緒池的好處 執行緒池原理 執行緒池狀態 執行緒池常用方法 使用ThreadPoolExecutor建立執行緒池 執行緒池 執行緒資源必須通過執行緒池提供,不允許在應用中

Java併發程式設計執行(三)

一.介紹 Java通過Executors提供四種執行緒池,分別為: (1)newCachedThreadPool:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。 (2)newFixedThreadPool: 建立一個定長執行緒池,可控制

Java執行-併發執行

執行緒池有了解嗎? 答: java.util.concurrent.ThreadPoolExecutor 類就是一個執行緒池。客戶端呼叫ThreadPoolExecutor.submit(Runnable task) 提交任務,執行緒池內部維護的工作者執行緒的數量就是該執行緒池的執行

Java執行系列--“JUC執行”01 執行架構

概要 前面分別介紹了”Java多執行緒基礎”、”JUC原子類”和”JUC鎖”。本章介紹JUC的最後一部分的內容——執行緒池。內容包括: 執行緒池架構圖 執行緒池示例 執行緒池架構圖 執行緒池的架構圖如下: 1、Executor

Java執行系列--“JUC執行”05 執行原理(四)

概要 本章介紹執行緒池的拒絕策略。內容包括: 拒絕策略介紹 拒絕策略對比和示例 拒絕策略介紹 執行緒池的拒絕策略,是指當任務新增到執行緒池中被拒絕,而採取的處理措施。 當任務新增到執行緒池中之所以被拒絕,可能是由於:第一,執行緒池異常關閉。第二,任務數量

Java併發程式設計實戰》筆記3——執行的使用

1、執行緒飢餓死鎖 線上程池中,如果任務依賴於其他任務,那麼可能發生死鎖。在單執行緒的Executor中,如果一個任務將另一個任務提交到同一個Executor,並且等待這個被提交任務的結果,那麼通常會引發死鎖。 如下面程式碼所示: public class Thread

(十七)java併發程式設計--任務執行執行的使用

大多數併發程式圍繞著”任務執行”來構造的: 任務通常是一些抽象的且離散的工作單元。通過把一個用程式的共工作分解到多個任務中,可以簡化程式的組織結構,提供一種自然的事務邊界來優化錯誤恢復過程,以及提供一種自然的工作結構來提升併發性。 1 線上程中執行任務

java併發程式設計執行

前言 本文介紹幾種java常用的執行緒池如:FixedThreadPool,ScheduledThreadPool,CachedThreadPool等執行緒池,並分析介紹Executor框架,做到“知其然”:會用執行緒池,正確使用執行緒池。並且“知其所以然”: