一. 準備工作 1. 本文參考 Java併發編程:線程池的使用 二. 相關代碼文件介紹 1. ThreadPoolExecutor.java 線程池中最核心的一個類,提供了四個構造函數用於創建線程池 public class ThreadPoolExecutor extends AbstractEx ...
一. 準備工作
1. 本文參考 Java併發編程:線程池的使用
二. 相關代碼文件介紹
1. ThreadPoolExecutor.java 線程池中最核心的一個類,提供了四個構造函數用於創建線程池
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class ThreadPoolExecutor extends 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); ... }View Code
下麵解釋下一下構造器中各個參數的含義:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
corePoolSize:核心池的大小。在創建了線程池後,預設情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務, 除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思, 即在沒有任務到來之前就創建corePoolSize個線程或者一個線程。預設情況下,在創建了線程池後,線程池中的線程數為0,當有任務 來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中; maximumPoolSize:線程池最大線程數,它表示線上程池中最多能創建多少個線程; keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。預設情況下,只有當線程池中的線程數大於corePoolSize時, keepAliveTime才會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閑的 時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean) 方法,線上程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數為0; unit:參數keepAliveTime的時間單位; workQueue:一個阻塞隊列,用來存儲等待執行的任務; threadFactory:線程工廠,主要用來創建線程; handler:表示當拒絕處理任務時的策略;View Code
2. ThreadPoolExecutor類中有幾個重要的方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
execute():通過這個方法可以向線程池提交一個任務,交由線程池去執行。
submit():這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果
shutdown()和shutdownNow():是用來關閉線程池的
View Code
三. 深入剖析線程池實現原理
1. 線程池狀態
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
RUNNING 當創建線程池後,初始時;
SHUTDOWN 當調用了shutdown()方法,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;
STOP 當調用了shutdownNow()方法,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務;
TERMINATED 當線程池處於SHUTDOWN或STOP狀態,並且所有工作線程已經銷毀,任務緩存隊列已經清空或執行結束後;
View Code
2. 任務的執行
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
a. 如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務; b. 如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閑線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務; c. 如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理; d. 如果線程池中的線程數量大於 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許為核心池中的線程設置存活時間,那麼核心池中的線程空閑時間超過keepAliveTime,線程也會被終止。View Code
3. 線程池中的線程初始化
預設情況下,創建線程池之後,線程池中是沒有線程的,需要提交任務之後才會創建線程。
在實際中如果需要線程池創建之後立即創建線程,可以通過以下兩個方法辦到:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
prestartCoreThread():初始化一個核心線程;
prestartAllCoreThreads():初始化所有核心線程
View Code
4. 任務緩存隊列及排隊策略
workQueue的類型為BlockingQueue<Runnable>,通常可以取下麵三種類型:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1)ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小; 2)LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則預設為Integer.MAX_VALUE; 3)synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。View Code
5. 任務拒絕策略
當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重覆此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
View Code
四. 使用示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Test { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5)); for(int i=0;i<15;i++){ MyTask myTask = new MyTask(i); executor.execute(myTask); System.out.println("線程池中線程數目:"+executor.getPoolSize()+",隊列中等待執行的任務數目:"+ executor.getQueue().size()+",已執行玩別的任務數目:"+executor.getCompletedTaskCount()); } executor.shutdown(); } } class MyTask implements Runnable { private int taskNum; public MyTask(int num) { this.taskNum = num; } @Override public void run() { System.out.println("正在執行task "+taskNum); try { Thread.currentThread().sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task "+taskNum+"執行完畢"); } }View Code
執行結果:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
正在執行task 0 線程池中線程數目:1,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 線程池中線程數目:2,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 1 線程池中線程數目:3,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 2 線程池中線程數目:4,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 3 線程池中線程數目:5,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 4 線程池中線程數目:5,隊列中等待執行的任務數目:1,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:2,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:3,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:4,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 線程池中線程數目:6,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 10 線程池中線程數目:7,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 11 線程池中線程數目:8,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 12 線程池中線程數目:9,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 13 線程池中線程數目:10,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 14 task 3執行完畢 task 0執行完畢 task 2執行完畢 task 1執行完畢 正在執行task 8 正在執行task 7 正在執行task 6 正在執行task 5 task 4執行完畢 task 10執行完畢 task 11執行完畢 task 13執行完畢 task 12執行完畢 正在執行task 9 task 14執行完畢 task 8執行完畢 task 5執行完畢 task 7執行完畢 task 6執行完畢 task 9執行完畢View Code
從執行結果可以看出,當線程池中線程的數目大於5時,便將任務放入任務緩存隊列裡面,當任務緩存隊列滿了之後,便創建
新的線程。如果上面程式中,將for迴圈中改成執行20個任務,就會拋出任務拒絕異常了。
五. Executors類
在java中,並不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個靜態方法來創建線程池:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Executors.newCachedThreadPool(); //創建一個緩衝池,緩衝池容量大小為Integer.MAX_VALUE Executors.newSingleThreadExecutor(); //創建容量為1的緩衝池 Executors.newFixedThreadPool(int); //創建固定容量大小的緩衝池View Code
下麵是這三個靜態方法的具體實現;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }View Code
下麵是對這三個方法的解釋:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
newFixedThreadPool創建的線程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor將corePoolSize和maximumPoolSize都設置為1,也使用的LinkedBlockingQueue;
newCachedThreadPool將corePoolSize設置為0,將maximumPoolSize設置為Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就創建線程運行,當線程空閑超過60秒,就銷毀線程。
View Code
六. 如何合理配置線程池的大小,僅供參考
一般需要根據任務的類型來配置線程池大小:
如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設為 NCPU+1
如果是IO密集型任務,參考值可以設置為2*NCPU
當然,這隻是一個參考值,具體的設置還需要根據實際情況進行調整。