Java多執行緒-----執行緒池的使用,原理以及舉例實現(三)(四):使用樣例及如何配置執行緒池大小
三.使用示例
前面我們討論了關於執行緒池的實現原理,這一節我們來看一下它的具體使用:
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+"執行完畢"); } }
執行結果:
正在執行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執行完畢
從執行結果可以看出,當執行緒池中執行緒的數目大於5時,便將任務放入任務快取佇列裡面,當任務快取佇列滿了之後,便建立新的執行緒。如果上面程式中,將for迴圈中改成執行20個任務,就會丟擲任務拒絕異常了。
不過在java doc中,並不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個靜態方法來建立執行緒池:
Executors.newCachedThreadPool(); //建立一個緩衝池,緩衝池容量大小為Integer.MAX_VALUE Executors.newSingleThreadExecutor(); //建立容量為1的緩衝池 Executors.newFixedThreadPool(int); //建立固定容量大小的緩衝池
下面是這三個靜態方法的具體實現;
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>());
}
從它們的具體實現來看,它們實際上也是呼叫了ThreadPoolExecutor,只不過引數都已配置好了。
newFixedThreadPool建立的執行緒池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
newSingleThreadExecutor將corePoolSize和maximumPoolSize都設定為1,也使用的LinkedBlockingQueue;
newCachedThreadPool將corePoolSize設定為0,將maximumPoolSize設定為Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就建立執行緒執行,當執行緒空閒超過60秒,就銷燬執行緒。
實際中,如果Executors提供的三個靜態方法能滿足要求,就儘量使用它提供的三個方法,因為自己去手動配置ThreadPoolExecutor的引數有點麻煩,要根據實際任務的型別和數量來進行配置。
另外,如果ThreadPoolExecutor達不到要求,可以自己繼承ThreadPoolExecutor類進行重寫。
四.如何合理配置執行緒池的大小
本節來討論一個比較重要的話題:如何合理配置執行緒池大小,僅供參考。
一般需要根據任務的型別來配置執行緒池大小:
如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設為 NCPU+1
如果是IO密集型任務,參考值可以設定為2*NCPU
當然,這只是一個參考值,具體的設定還需要根據實際情況進行調整,比如可以先將執行緒池大小設定為參考值,再觀察任務執行情況和系統負載、資源利用率來進行適當調整。