JAVA之線程池的使用

在很多的時候我們需要用到線程池,比如說jdbc的連接池之類的東西,這樣能有效的減少線程創建和回收過程當中造成的性能損耗!尤其是在高併發下,線程池能夠顯著的節約線程的創建成本,下面我以我的理解來講講JAVA中線程池的簡單使用!

一.ThreadPoolExecutor類

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

查看其源碼可以看到,ThreadPoolExcutor類中最重要的構造方法就是如上這個構造方法,剩餘的構造方法都是調用這個方法來產生線程池的,接着我們來了解下這些輸入參數的具體含義!

/**
     * 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
     */

相信上邊的這段話已經很詳細的講述了各個參數的含義我在對以上的解釋做如下的說明:

其中unit可以由枚舉類TimeUnit得到主要有以下的單位:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒

對於其中的緩存隊列workQueue有如下的選項:

//以下的三個阻塞隊列用得較多是第二個
ArrayBlockingQueue; //可以指定長度
LinkedBlockingQueue; //默認無限大
SynchronousQueue; 

接下來比較重要的就是拋棄策略了有如下的四種拋棄策略:

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。 
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。 
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

二.ThreadPoolExecutor類使用實例

public class MyTask implements Runnable {
    private int num;
    public MyTask(int num){
        this.num= num;
    }

    @Override
    public void run() {
        System.out.println("第"+num+"個任務開始執行:start!");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("第"+num+"個任務執行完畢:end!");
    }
}

//主方法
public class TestPool {
    public static void main(String args[]) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5,10,200,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));
        for(int i=0;i<15;i++){
            Runnable task = new MyTask(i);
            pool.execute(task);
            System.out.println("線程池中有線程:"+pool.getPoolSize()+"個,隊列中等待執行的task:"+pool.getQueue().size()+"個,已經完成task:"+pool.getCompletedTaskCount());
        }
        pool.shutdown();
    }
}

我們直接來查看運行的結果:

第0個任務開始執行:start!
線程池中有線程:1個,隊列中等待執行的task:0個,已經完成task:0
線程池中有線程:2個,隊列中等待執行的task:0個,已經完成task:0
第1個任務開始執行:start!
線程池中有線程:3個,隊列中等待執行的task:0個,已經完成task:0
第2個任務開始執行:start!
線程池中有線程:4個,隊列中等待執行的task:0個,已經完成task:0
第3個任務開始執行:start!
線程池中有線程:5個,隊列中等待執行的task:0個,已經完成task:0
第4個任務開始執行:start!
線程池中有線程:5個,隊列中等待執行的task:1個,已經完成task:0
線程池中有線程:5個,隊列中等待執行的task:2個,已經完成task:0
線程池中有線程:5個,隊列中等待執行的task:3個,已經完成task:0
線程池中有線程:5個,隊列中等待執行的task:4個,已經完成task:0
線程池中有線程:5個,隊列中等待執行的task:5個,已經完成task:0
線程池中有線程:6個,隊列中等待執行的task:5個,已經完成task:0
第10個任務開始執行:start!
線程池中有線程:7個,隊列中等待執行的task:5個,已經完成task:0
第11個任務開始執行:start!
線程池中有線程:8個,隊列中等待執行的task:5個,已經完成task:0
第12個任務開始執行:start!
線程池中有線程:9個,隊列中等待執行的task:5個,已經完成task:0
第13個任務開始執行:start!
線程池中有線程:10個,隊列中等待執行的task:5個,已經完成task:0
第14個任務開始執行:start!
第0個任務執行完畢:end!
第1個任務執行完畢:end!
第5個任務開始執行:start!
第6個任務開始執行:start!
第2個任務執行完畢:end!
第3個任務執行完畢:end!
第4個任務執行完畢:end!
第8個任務開始執行:start!
第7個任務開始執行:start!
第9個任務開始執行:start!
第10個任務執行完畢:end!
第11個任務執行完畢:end!
第12個任務執行完畢:end!
第13個任務執行完畢:end!
第14個任務執行完畢:end!
第8個任務執行完畢:end!
第6個任務執行完畢:end!
第5個任務執行完畢:end!
第7個任務執行完畢:end!
第9個任務執行完畢:end!

Process finished with exit code 0

三.使用分析

現在我們來分析上述的打印結果,首先必須說明的是我設置的corePoolSize=5,maxPoolSize=10,緩存隊列workQueue=5。在使用線程池的時候,默認是不會預先建線程的,可以使用如下的方法設置預先生成線程:

prestartCoreThread():初始化一個核心線程;
prestartAllCoreThreads():初始化所有核心線程

1)當有一個任務提交到線程池的時,如果線程池中pool.getPoolSize()<=corePoolSize中那麼會新建一個線程去執行該任務

2)當pool.getPoolSize()>corePoolSize的時候,每來一個任務都會存儲到緩存隊列workQueue中,如果添加成功,就等待空閒線程取出該任務去執行,如果添加失敗也就是緩存隊列滿了,那麼線程池會新建線程執行緩存隊列中的任務這時要求pool.getPoolSize<=maxPoolSize;與此同時keepAliveTime同樣會起作用在超過corePoolSize規定數量的空閒線程上

3)當緩存隊列滿且pool.getPoolSize()>maxPoolSize的時候就會採取拋棄策略來丟棄任務了

通過上邊的說明可以瞭解到,使用線程池雖然簡單,但是如果需要高效低耗的使用線程池卻並不是一件容易的事情,你需要合理的定義每一個值並且選用合理的緩存隊列和好的拋棄策略,合理的定義一個值十分的簡單,但是合理的定義這麼多值還是需要進行一些調整和測試的,這顯然不利於我們簡單的使用線程池,於是乎定製化適用於確切場景的線程池出現了!

四.定製化的線程池

1)Executors.newCachedThreadPool()

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

* 它是一個可以無限擴大的線程池;
* 它比較適合處理執行時間比較小的任務;
* corePoolSize爲0,maximumPoolSize爲無限大,意味着線程數量可以無限大; 
* keepAliveTime爲60S,意味着線程空閒時間超過60S就會被殺死;
* 採用SynchronousQueue裝等待的任務,這個阻塞隊列沒有存儲空間,這意味着只要有請求到來,就必須要找到一條工作線程處理他,如果當前沒有空閒的線程,那麼就會再創建一條新的線程。

2)Executors.newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

* 它是一種固定大小的線程池;
* corePoolSize和maximunPoolSize都爲用戶設定的線程數量nThreads;
* keepAliveTime爲0,意味着一旦有多餘的空閒線程,就會被立即停止掉;
* 但這裏keepAliveTime無效;阻塞隊列採用了LinkedBlockingQueue,它是一個無界隊列;
* 由於阻塞隊列是一個無界隊列,因此永遠不可能拒絕任務;
* 由於採用了無界隊列,實際線程數量將永遠維持在nThreads,因此maximumPoolSize和keepAliveTime將無效。

3)Executors.newSingleThreadPool()
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

* 它只會創建一條工作線程;

* 採用的阻塞隊列是LinkedBlockingQueue;

結語:以上就是線程池的基本使用的內容了,看起來還是比較簡單的,寫起來自己以後忘記的時候也能好好回顧下!

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