Java線程池的典型用法.md

Java中線程的出現通常是實現異步處理的功能,我們創建和使用一個線程非常簡單,但是有一個問題:
如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因爲頻繁創建線程和銷燬線程需要時間。
爲了解決這個問題,線程池就出現了。線程池可以使得線程執行完一個任務之後,不被銷燬進而複用,從而避免了頻繁創建和銷燬線程的資源消耗。
Java中線程池對應的頂級接口是ExecutorService,最核心的實現類是ThreadPoolExecutor。

1.ThreadPoolExecutor構造參數

ExecutorService是Java中對線程池定義的一個接口,Java API對ExecutorService接口的實現有兩個,ThreadPoolExecutor和ScheduledThreadPoolExecutor。我們主要關注
ThreadPoolExecutor。
下面我們看下ThreadPoolExecutor的一個構造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) 

參數解釋:

  1. corePoolSize 核心線程池大小,在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務.當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;
  2. maximumPoolSize 線程池最大容量大小,它表示在線程池中最多能創建多少個線程
  3. keepAliveTime 線程池空閒時(即沒有任務執行),線程存活的時間。默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,線程池中的線程數大於corePoolSize時,如果一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。
  4. TimeUnit 時間單位
  5. BlockingQueue 一個阻塞隊列,用來存儲等待執行的任務。這裏的阻塞隊列有ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;對應不同的排隊策略。一般使用LinkedBlockingQueue。

LinkedBlockingQueue實現是線程安全的,實現了先進先出等特性,是作爲生產者消費者的首選;
put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來

  1. ThreadFactory 線程工廠,主要用來創建線程。默認使用DefaultThreadFactory

2.線程池的運行過程

1.調用ThreadPoolExecutor的execute提交線程,首先檢查CorePool,如果CorePool內的線程小於CorePoolSize,新創建線程執行任務。
2.如果當前CorePool內的線程大於等於CorePoolSize,那麼將線程加入到BlockingQueue。
3.如果不能加入BlockingQueue,在小於MaxPoolSize的情況下創建線程執行任務。
4.如果線程數大於等於MaxPoolSize,那麼執行拒絕策略

ThreadPoolExecutor被初始化好之後便可以提交線程任務,線程的提交方法主要是execute和submit。

3.線程池的典型用法

public class ThreadPoolTest {

    private static ThreadPoolExecutor executor =  createThreadPool();
    private static ReentrantLock reentrantLock = new ReentrantLock();
    private static ThreadPoolExecutor createThreadPool() {
        int corePoolSize = 3;
        int maximumPoolSize = 5;
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.MINUTES, workQueue);
        return executor;
    }

    public static class T implements Runnable{
        int i;

        public T(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            System.out.println("task "+i+"執行完畢");
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<15;i++){
            executor.execute(new T(i));
            System.out.println("線程池中線程數目:"+executor.getPoolSize()+",隊列中等待執行的任務數目:"+
                    executor.getQueue().size()+",已執行完的任務數目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }

}

上面的代碼,設置了3個核心線程,最大線程數爲5,阻塞隊列的容量爲5.我們想執行15個線程。我們看一下結果:

線程池中線程數目:1,隊列中等待執行的任務數目:0,已執行完的任務數目:0
線程池中線程數目:2,隊列中等待執行的任務數目:0,已執行完的任務數目:0
線程池中線程數目:3,隊列中等待執行的任務數目:0,已執行完的任務數目:0
線程池中線程數目:3,隊列中等待執行的任務數目:1,已執行完的任務數目:0
線程池中線程數目:3,隊列中等待執行的任務數目:2,已執行完的任務數目:0
線程池中線程數目:3,隊列中等待執行的任務數目:3,已執行完的任務數目:0
線程池中線程數目:3,隊列中等待執行的任務數目:4,已執行完的任務數目:0
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.wy.thread.ThreadPoolTest$T@69d0a921 rejected from java.util.concurrent.ThreadPoolExecutor@446cdf90[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]
 at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
線程池中線程數目:3,隊列中等待執行的任務數目:5,已執行完的任務數目:0
線程池中線程數目:4,隊列中等待執行的任務數目:5,已執行完的任務數目:0
 at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
線程池中線程數目:5,隊列中等待執行的任務數目:5,已執行完的任務數目:0
 at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
 at com.wy.thread.ThreadPoolTest.main(ThreadPoolTest.java:38)
task 0執行完畢
task 3執行完畢
task 4執行完畢
task 5執行完畢
task 6執行完畢
task 7執行完畢
task 1執行完畢
task 2執行完畢
task 8執行完畢
task 9執行完畢

看下過程,
1.首先前3個線程是創建的核心線程
2.從第4個線程開始線程池中線程數目不再變化,一直是核心線程數3,新添加的任務被添加到阻塞隊列中
3.當提交到第8個線程時,阻塞隊列中任務的個數爲5已經滿了,拋出了RejectedExecutionException
4.當提交到第9個線程時,阻塞隊列中已經不能添加任務了,線程池又創建了一個線程
5.當提交到第10個線程後,線程池最大線程數也滿了,拋出RejectedExecutionException
我們將阻塞隊列的大小改爲20之後,就不會拋出RejectedExecutionException,而且線程池中的線程個數也一直是3.

4.配置線程池的核心線程大小

一般需要根據任務的類型來配置線程池大小,以下是參考公式,可根據實際情況做相應調整:

  • CPU密集型任務,就需要儘量壓榨CPU,CorePoolSize值可以設爲 NCPU+1

  • IO密集型任務,CorePoolSize值可以設置爲2*NCPU

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