文章目錄
線程池 —— 認識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