線程池 —— 認識ThreadPoolExecuotr

線程池 —— 認識ThreadPoolExecuotr

ThreadPoolExecuotr 構造函數源碼(JDK 1.8)

   /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

參數含義

  • corePoolSize:
    初始線程數,除非設置了allowCoreThreadTimeOut(在源碼中處理時會體現的變量).
  • maximumPoolSize:
    線程池中最大的線程數量,超過最大的線程數,後續提交的任務都會被RejectedExecutionHandler拒絕。
  • keepAliveTime
    調用線程池會增加線程,就算初始線程還有值,當線程超過corePoolSize時,會進入Queue中,當線程總數超過maximumPoolSize時,超過一定時間(keepAliveTime)會執行RejectedExecutionHandler(飽和策略)
  • workQueue
    等待隊列(如果線程池中的線程數量大於或者等於corePoolSize的時候,把該任務封裝成一個work對象,放入到等待隊列中,因爲隊列種類很多,使用不同隊列就會執行不同排隊機制),隊列是要通過execute方法提交的runnable任務.
  • threadFactory:
    創建新線程使用的工廠
  • unit
    keepAliveTime的單位
  • handler
    線程池飽和策略(阻塞隊列滿了,並且沒有空閒的線程,這時,如果繼續提交任務,就需要採用一種策略處理該任務,線程池提供五種策略:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy、自定義策略(通過實現RejectedExecutionHandler接口))


執行線程

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    // 當線程小於核心線程時
    if (workerCountOf(c) < corePoolSize) {
    	// 創建線程(創建工作線程執行任務)
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 當前線程數目大於核心線程數目(corePoolSize),並且wokeQueue任務隊列沒滿,將任務放到隊列裏面
    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);
}

線程與線程池執行

線程處理流程與線程池執行示意圖

  • 1)如果當前運行的線程少於corePoolSize,則創新線程來執行任務(注意,執行這一步驟需要獲取全局鎖)
  • 2)如果運行的線程等於或多於corePoolSize,則將任務加入BlockingQueue
  • 3)如果無法將任務加入BlockingQueue(隊列已滿),則創建新的線程來處理任務(注意,執行這一步驟需要獲取全局鎖)
  • 4)如果創建新線程將是當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法(飽和策略)


BlockingQueue workQueue 隊列

分類

常用隊列主要有以下兩種:(通過不同的實現方式,還可以延伸出很多不同類型的隊列,DelayQueue就是其中的一種)

  • 先進先出(FIFO)
    先插入的隊列的元素也最先出隊列,類似於排隊的功能。某種意義說,這種隊列體現了“公平性”,比如:ReentrantLock
  • 後進先出(LIFO)
    後插入隊列的元素最先出隊列,這種隊列優先處理最近發生的事件。

核心方法介紹

思考:多線程環境下爲什麼需要BlockingQueue?

使用BlockingQueue可以 使開發者不用關心什麼時候需要阻塞線程,什麼時候喚醒線程,使用BlockingQueuew制定規則,令它可以自動將這些問題解決。下面介紹一些常用的BlockingQueue的實現。

放入數據
  • offer(anObject)
    將anObject放入BlockingQuque,即如果BlockingQueue可以容納,則返回true,否則返回false.(不阻塞當前執行方法的線程)
  • offer(E o, long timeout, TimeUnit unit)
    設置等待時間,如果在等待時間內,無法向隊列插入o,則返回失敗
  • put(anObject)
    將anObject放入BlockingQueue,如果BlockingQueue沒有空間,則調用此方法的線程被阻塞,直到BlockingQueue裏面有空間再進行放入anObject。3
獲取數據
  • poll(long timeout, TimeUnit unit)
    在規定時間內從BlockingQueue隊首中取出對象,如果在指定時間內沒有取出,則返回失敗。
  • take()
    取走BlockingQueue隊首對象,若BlockingQueue爲空,讓當前線程池等待(不允許有新的線程進入隊列),直到獲取到要的對象
  • drainTo()
    一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數),通過該方法,可以提升獲取數據效率;不需要多次分批加鎖或釋放鎖

常用隊列

以下五種阻塞隊列都是BlockingQueue接口的實現

ArrayBlockingQueue 數組阻塞隊列
  • 基於數組的阻塞隊列實現
  • 內部維護一個定長數組,用於緩存隊列中的數據對象,是一個較爲常用的阻塞隊列
  • ArrayBlockingQueue內部還保存着兩個整形變量,分別表示隊列的頭部和尾部在數組中的位置
  • 生產者放入數據,消費者獲取數據,都是共用同一個鎖對象,因此兩者不能真正並行運行,這點 與LinkedBlockingQueue截然不同
  • ArrayBlockingQueue默認採用非公平鎖,可以手動控制對象的內部鎖爲公平鎖
  • ArrayBlockingQueue和LinkedBlockingQueue明顯區別
    • ArrayBlockingQueue在插入或刪除元素時不會產生或銷燬任何額外的對象實例
    • LinkedBlockingQueue會生成一個額外的Node對象
LinkedBlockingQueue 鏈表阻塞隊列
  • 基於鏈表的阻塞隊列實現
  • 與ArrayBlockingQueue類似,內部同樣維護一個數據緩衝隊列(該隊列由一個鏈表構成)
  • 當生產者往隊列放入一個數據時,隊列會從生產者手中獲取數據,並緩存在隊列內部,而生產者立即返回
  • 只有當隊列緩衝區達到最大值緩存容量時(LinkedBlockingQueue可以通過構造函數設置該值),纔會阻塞生產隊列,直到消費者從隊列中消費掉一份數據,生產者線程會被喚醒,同理,消費者端處理也是基於同樣原理。
  • LinkedBlockingQueue之所以能夠高效的處理併發數據,因爲生產者端和消費者端分別採用了獨立的鎖來控制數據同步,支持高併發場景下生產者與消費者並行操作隊列中的數據
  • 如果不指定LinkedBlockingQueue大小,默認是無限大小的容量(Integer.MAX_VALUE),如果生產者速度大於消費者速度,可以導致內存不夠用的情況

注:Executors.newFixedThreadPool(int) 的實現是LinkedBlockingQueue;
注:Executors.newSingleThreadExecutor() 的實現是LinkedBlockingQueue;

DelayQueue 延時隊列
  • DelayQueue中的元素只有在指定的延遲時間結束後,才能夠從隊列中獲取到元素
  • DelayQueue 是一個沒有大小限制的隊列
  • 插入數據操作(生產者)永遠不會被阻塞,而獲取數據操作(消費者)纔會被阻塞
PriorityBlockingQueue 優先級隊列

基於優先級的阻塞隊列(優先級的判斷通過構造函數傳入的Compator對象來決定)

  • PriorityBlockingQueue 不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者
  • 所以在使用的時候要特別注意,生產者生產數據的速度絕對不能快於消費者消費數據的速度,否則時間一長,會最終耗盡所有的可用堆內存空間。在實現PriorityBlockingQueue時,內部控制線程同步的鎖採用的是公平鎖。
SynchronousQueue 同步隊列

一種無緩衝的等待隊列,類似於無中介的直接交易,有點像原始社會中的生產者和消費者,生產者拿着產品去集市銷售給產品的最終消費者,而消費者必須親自去集市找到所要商品的直接生產者,如果一方沒有找到合適的目標,那麼對不起,大家都在集市等待。相對於有緩衝的BlockingQueue來說,少了一箇中間經銷商的環節(緩衝區),如果有經銷商,生產者直接把產品批發給經銷商,而無需在意經銷商最終會將這些產品賣給那些消費者,由於經銷商可以庫存一部分商品,因此相對於直接交易模式,總體來說採用中間經銷商的模式會吞吐量高一些(可以批量買賣);但另一方面,又因爲經銷商的引入,使得產品從生產者到消費者中間增加了額外的交易環節,單個產品的及時響應性能可能會降低。

兩種聲明方式
  • 公平模式
    SynchronousQueue會採用公平鎖,並配合一個FIFO隊列來阻塞多餘的生產者和消費者,從而體現整體的公平策略;
  • 非公平模式(默認)
    SynchronousQueue採用非公平鎖,同時配合一個LIFO隊列來管理多餘的生產者和消費者,而後一種模式,如果生產者和消費者的處理速度有差距,則 很容易出現飢渴的情況,即可能有某些生產者或者是消費者的數據永遠都得不到處理。

注:Executors.newCachedThreadPool();的實現是SynchronousQueue;

性能測試

參考:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue性能測試(轉)

package com.java.offer.frank.thread.action.demo;

import java.util.concurrent.*;

/**
 * 測試ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue性能
 */
public class BlockingQueueTest {

    private static int THREAD_NUM;
    private static int N = 1000000;
    private static ExecutorService executor;
    public static void main(String[] args) throws Exception {
        System.out.println("Producer\tConsumer\tcapacity \t LinkedBlockingQueue \t ArrayBlockingQueue \t SynchronousQueue");

        for(int j = 0; j<10; j++){
            THREAD_NUM = (int) Math.pow(2, j);
            executor = Executors.newFixedThreadPool(THREAD_NUM * 2);

            for (int i = 0; i < 10; i++) {
                int length = (i == 0) ? 1 : i * 10;
                System.out.print(THREAD_NUM + "\t\t");
                System.out.print(THREAD_NUM + "\t\t");
                System.out.print(length + "\t\t");
                System.out.print(doTest2(new LinkedBlockingQueue<Integer>(length), N) + "/s\t\t\t");
                System.out.print(doTest2(new ArrayBlockingQueue<Integer>(length), N) + "/s\t\t\t");
                System.out.print(doTest2(new SynchronousQueue<Integer>(), N) + "/s");
                System.out.println();
            }

            executor.shutdown();
        }
    }

    private static class Producer implements Runnable{
        int n;
        BlockingQueue<Integer> q;

        public Producer(int initN, BlockingQueue<Integer> initQ){
            n = initN;
            q = initQ;
        }

        public void run() {
            for (int i = 0; i < n; i++)
                try {
                    q.put(i);
                } catch (InterruptedException ex) {
                }
        }
    }

    private static class Consumer implements Callable<Long>{
        int n;
        BlockingQueue<Integer> q;

        public Consumer(int initN, BlockingQueue<Integer> initQ){
            n = initN;
            q = initQ;
        }

        public Long call() {
            long sum = 0;
            for (int i = 0; i < n; i++)
                try {
                    sum += q.take();
                } catch (InterruptedException ex) {
                }
            return sum;
        }
    }

    private static long doTest2(final BlockingQueue<Integer> q, final int n)
            throws Exception {
        CompletionService<Long> completionServ = new ExecutorCompletionService<Long>(executor);

        long t = System.nanoTime();
        for(int i=0; i<THREAD_NUM; i++){
            executor.submit(new Producer(n/THREAD_NUM, q));
        }
        for(int i=0; i<THREAD_NUM; i++){
            completionServ.submit(new Consumer(n/THREAD_NUM, q));
        }

        for(int i=0; i<THREAD_NUM; i++){
            completionServ.take().get();
        }

        t = System.nanoTime() - t;
        return (long) (1000000000.0 * N / t); // Throughput, items/sec
    }
}

  • 執行結果
    配置:WIN10
    在這裏插入圖片描述
...
...
Connected to the target VM, address: '127.0.0.1:55238', transport: 'socket'
Producer	Consumer	capacity 	 LinkedBlockingQueue 	 ArrayBlockingQueue 	 SynchronousQueue
1		1		1		91965/s			91959/s			366617/s
1		1		10		953696/s			943459/s			277284/s
1		1		20		1904141/s			1926988/s			265989/s
1		1		30		2968361/s			2916859/s			255032/s
1		1		40		4548421/s			3855967/s			222687/s
1		1		50		9430599/s			4767307/s			241964/s
1		1		60		7318153/s			5609283/s			238902/s
1		1		70		7439673/s			6738054/s			247625/s
1		1		80		5553072/s			7697482/s			279672/s
1		1		90		4524037/s			9025759/s			252031/s
2		2		1		90973/s			91514/s			954934/s
2		2		10		713678/s			527480/s			985417/s
2		2		20		1443258/s			1119546/s			1036098/s
2		2		30		1983797/s			1806370/s			1195615/s
2		2		40		2964555/s			2051601/s			985493/s
2		2		50		3795900/s			2762851/s			919701/s
2		2		60		4799079/s			3248316/s			903594/s
2		2		70		5364073/s			3854182/s			987358/s
2		2		80		6235140/s			4401578/s			1257740/s
2		2		90		6661896/s			5160448/s			1113124/s
4		4		1		92107/s			90642/s			2689548/s
4		4		10		1726540/s			358131/s			2679777/s
4		4		20		3353265/s			1123323/s			2720432/s
4		4		30		4925678/s			1567281/s			2727719/s
4		4		40		5911964/s			2110198/s			2724256/s
4		4		50		6628860/s			2467913/s			2750098/s
4		4		60		6921215/s			2164982/s			2719990/s
4		4		70		6805804/s			4662687/s			2701881/s
4		4		80		7344165/s			3536709/s			2708836/s
4		4		90		7027673/s			3130522/s			2660962/s
8		8		1		93545/s			92869/s			2201049/s
8		8		10		1733532/s			463352/s			2215217/s
8		8		20		3115509/s			703680/s			2225547/s
8		8		30		4586333/s			1515047/s			2297381/s
8		8		40		5449321/s			1602711/s			2320047/s
8		8		50		6510586/s			1663456/s			2247328/s
8		8		60		6781858/s			2632964/s			2272160/s
8		8		70		6758889/s			3239661/s			2158761/s
8		8		80		7336902/s			3989830/s			2200392/s
8		8		90		7487888/s			3542118/s			2275863/s
16		16		1		97642/s			90259/s			2140706/s
16		16		10		1643182/s			341132/s			2313325/s
16		16		20		3142675/s			754738/s			2142465/s
16		16		30		4761555/s			1351709/s			2056255/s
16		16		40		5872804/s			2062390/s			2221156/s
16		16		50		6232164/s			2345968/s			2177814/s
16		16		60		6875158/s			2773055/s			2112430/s
16		16		70		6949676/s			3249319/s			2239116/s
16		16		80		7173323/s			3919831/s			2077181/s
16		16		90		7478145/s			4756161/s			2073870/s
32		32		1		110686/s			91912/s			2088903/s
32		32		10		1694806/s			279348/s			2032431/s
32		32		20		3290346/s			865011/s			2016924/s
32		32		30		4795427/s			1834324/s			2049761/s
32		32		40		5911143/s			2177243/s			2177071/s
32		32		50		6106941/s			2461343/s			2082106/s
32		32		60		6825300/s			3015013/s			2045709/s
32		32		70		6746067/s			3066112/s			2196008/s
32		32		80		6985215/s			3824910/s			2190782/s
32		32		90		7457716/s			3463629/s			2164759/s
64		64		1		207880/s			91256/s			2087170/s
64		64		10		1683912/s			389424/s			2079147/s
64		64		20		3238991/s			733675/s			2118551/s
64		64		30		4378511/s			1336209/s			2234310/s
64		64		40		5519208/s			1547645/s			2194217/s
64		64		50		5888789/s			2605918/s			2109774/s
64		64		60		6709076/s			2682557/s			2096178/s
64		64		70		6655879/s			3277397/s			2071702/s
64		64		80		7081445/s			3737165/s			2115328/s
64		64		90		7126115/s			3994240/s			2091770/s
128		128		1		204554/s			87075/s			2176845/s
128		128		10		1619171/s			328204/s			2181884/s
128		128		20		3092636/s			533778/s			2066740/s
128		128		30		4360306/s			1017100/s			2141616/s
128		128		40		4991422/s			1773710/s			2060649/s
128		128		50		5833475/s			1846381/s			2063006/s
128		128		60		6001032/s			2065971/s			2079865/s
128		128		70		6661572/s			2430652/s			2028861/s
128		128		80		5994898/s			2799312/s			2048669/s
128		128		90		6704839/s			2725811/s			2031984/s
256		256		1		158258/s			79638/s			2068807/s
256		256		10		1451439/s			285390/s			1967928/s
256		256		20		2775005/s			690559/s			2036268/s
256		256		30		3790749/s			1025905/s			2149660/s
256		256		40		4822570/s			1356318/s			2000817/s
256		256		50		5570580/s			1849989/s			2041918/s
256		256		60		6021020/s			2767182/s			1989533/s
256		256		70		6386752/s			2752471/s			2210044/s
256		256		80		6626615/s			2916560/s			2084160/s
256		256		90		6148756/s			2529608/s			2060077/s
512		512		1		147759/s			75621/s			2082208/s
512		512		10		1351367/s			302206/s			1987115/s
512		512		20		2639394/s			908092/s			2076575/s
512		512		30		3561041/s			1478838/s			2009688/s
512		512		40		4947149/s			2257426/s			2045543/s
512		512		50		5480611/s			1580416/s			2141108/s
512		512		60		5857036/s			2182799/s			1971585/s
512		512		70		6434666/s			1973550/s			2021387/s
512		512		80		6291021/s			1907188/s			2096352/s
512		512		90		6658033/s			3990411/s			2108029/s
Disconnected from the target VM, address: '127.0.0.1:55238', transport: 'socket'
Process finished with exit code 0

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