1. 程式人生 > >Java執行緒池(ThreadPoolFactory)構造引數總結

Java執行緒池(ThreadPoolFactory)構造引數總結

今天看到關於執行緒池的一篇帖子,是關於面試時問到ThreadPoolFactory構造器時的一些問題,之前博主也學習過一些關於ThreadPoolFactory構造器的問題,但是一直沒有總結過,既然今天有時間,那麼就總結一下,避免有些同學走彎路(有些工作多年的老鳥也不一定能準確的說明coreSize,MaxSize,workQueueSize的關係),話不多說上乾貨。
下面來直接看ThreadPoolFactory最詳細的構造器結構

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; }

從上面的程式碼塊可以看出來在初始化時,對構造引數做了大小比較,一旦下面比較有一個為真就會丟擲異常

corePoolSize<0
maximumPoolSize<=0 
maximumPoolSize<corePoolSize
keepAliveTime<0

從構造引數的入口可以看出主要的幾個引數一共有如下幾個
int corePoolSize
int maximumPoolSize
long keepAliveTime
TimeUnit unit
BlockingQueue workQueue
ThreadFactory threadFactory
RejectedExecutionHandler handler
而一般最讓人迷糊的3個引數就是corePoolSize,maxmumPoolSize,workQueueSize
下面就針對這幾個引數做下總結:
當執行緒池初始化時,PoolSize為0,當有任務向執行緒中提交時,執行緒池開始建立執行緒,用來執行業務。

一、corePoolSize、maxmumPoolSize、workQueueSize引數關係總結

JobCount代表向執行緒池中新增的任務數

1.JobCount <=corePoolSize<maxmumPoolSize<workQueueSize
(當JobCount小於等於corePoolSize時,workQueueSize與maxmumPoolSize無效,執行緒池會建立與JobCount個數一致的執行緒來執行任務)

2.corePoolSize <JobCount <=workQueueSize <maxmumPoolSize
(當JobCount大於corePoolSize並且小於等於workQueueSize時maxmumPoolSize無效,超出corePoolSize的任務,會在阻塞對列中排隊,當corePoolSize中有執行緒閒置的時候,從workQueue中取出執行)

3.corePoolSize<workQueueSize<JobCount<maxmumPoolSize
(當JobCount大於corePoolSize時並且workQueue中也放滿,但JobCount小於maxmumPoolSize時,那麼執行緒池會建立執行緒來處理超出workQueueSize部分的任務)

4.corePoolSize<workQueueSize+maxmumPoolSize<JobCount
(當JobCount大於corePoolSize並且大於workQueueSize和maxmumPoolSize之和。那麼執行緒池將會呼叫handler.rejectedExecution方法)


二、RejectedExecutionHandler引數總結

上面說了corePoolSize,maxmumPoolSize,workQueueSize 3個引數的用法了,那麼接下來說一下RejectedExecutionHandler這個引數的意思。
從字面上理解為拒絕處理者,可以理解為當任務數大於maxmumPoolSize後的一個回撥方法,RejectedExecutionHandler本身是一個介面,jdk本身對他有4個實現類

CallerRunsPolicy //執行緒呼叫執行該任務的 execute 本身。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度

AbortPolicy(如不指定handler則預設這個) //處理程式遭到拒絕將丟擲執行時RejectedExecutionException;

DiscardPolicy //不能執行的任務將被刪除

DiscardOldestPolicy //如果執行程式尚未關閉,則位於工作佇列頭部的任務將被刪除,然後重試執行程式(如果再次失敗,則重複此過程)

三、KeepAliveTime、TimeUnit引數總結

TimeUnit引數很好理解,是一個用來當做單位的列舉。主要還是說說keepAliveTime這個引數,網上很多對這個引數都是幾句話帶過,說是用來控制執行緒回收時間的引數,確實他的作用就是這樣,但是看下面例子

public class Test {
    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1);
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 5, 5, TimeUnit.SECONDS, queue);
        System.out.println("pool初始化時PoolSize大小:"+poolExecutor.getPoolSize());
        System.out.println("pool初始化時active大小:"+poolExecutor.getActiveCount());
        poolExecutor.execute(new Test().new Task("1"));
        poolExecutor.execute(new Test().new Task("2"));
        poolExecutor.execute(new Test().new Task("3"));
        poolExecutor.execute(new Test().new Task("4"));
        try {
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("pool現在PoolSize大小:"+poolExecutor.getPoolSize());
        System.out.println("pool現在active大小:"+poolExecutor.getActiveCount());
        // poolExecutor.shutdown();
    }

    class Task implements Runnable {
        String name;

        public Task(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            int i=0;
            while (true) {
                i++;
                if (name.equals("4")) {
                    if (i==5) {
                        break;
                    }
                }
                if (name.equals("2")) {
                    if (i==2) {
                        break;
                    }
                }
                System.out.println(name);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

例子看上去不是太明白,但是keepAliveTime本身就不是很容易觸發
1.keepAliveTime和maximumPoolSize及BlockingQueue的型別均有關係。如果BlockingQueue是無界的,那麼永遠不會觸發maximumPoolSize,自然keepAliveTime也就沒有了意義

2.反之,如果核心數較小,有界BlockingQueue數值又較小,同時keepAliveTime又設的很小,如果任務頻繁,那麼系統就會頻繁的申請回收執行緒。
上面的例子總結起來就是
1.假設corePoolSize=1,QueueSize=1,MaxmumPoolSize=5
2.當初始化執行緒池的時候CorePoolSize為0,假設這個時候來了4個任務為t1、t2、t3、t4
3.那麼執行緒池會建立一個執行緒用來執行t1
4.接下來發現coreSize不足,那麼將t2放到Queue中
5.又發現QueuePool的大小不夠了,判斷MaxSize大小沒有超出,那麼接下來申請執行緒執行t3和t4
6.,執行了一會發現t4執行完畢了,(PS:注意了)這個時候不管keepaliveTime不管設定的多小,也不會回收的,因為QueuPool中還有一個t2沒有執行,那麼現在就會拿剛才用來執行t4的執行緒執行t2,執行的時候發現t2執行的很快,那麼現線上程池的狀態就是一共有3個執行緒,兩個屬於active的,那麼這個時候就靠keepAliveTime引數判斷回收時間了。

可以將TimeUnit的單位修改為NANOSECONDS比較兩次的輸出,就會明白了