1. 程式人生 > >Java多執行緒-----執行緒池的使用,原理以及舉例實現(三)(四):使用樣例及如何配置執行緒池大小

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

  當然,這只是一個參考值,具體的設定還需要根據實際情況進行調整,比如可以先將執行緒池大小設定為參考值,再觀察任務執行情況和系統負載、資源利用率來進行適當調整。