有界、無界佇列對ThreadPoolExcutor執行的影響
Java提供了4種執行緒池:
newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool,你可以通過Executors來例項化這四種執行緒池。這四種執行緒池都直接或者間接獲取的ThreadPoolExecutor例項 ,只是例項化時傳遞的引數不一樣。所以如果java提供的四種執行緒池滿足不了我們的需求,我們可以建立自定義執行緒池。
ThreadPoolExecutor的構造方法如下:
其中:
corePoolSize: 核心池的大小。 當有任務來之後,就會建立一個執行緒去執行任務,當執行緒池中的執行緒數目達到corePoolSize後,就會把到達的任務放到快取隊列當中;
maximumPoolSize: 執行緒池最大執行緒數,它表示線上程池中最多能建立多少個執行緒;
keepAliveTime: 表示執行緒沒有任務執行時最多保持多久時間會終止;
unit: 引數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小時
TimeUnit.MINUTES; //分鐘
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //納秒
workQueue: 一個阻塞佇列,用來儲存等待執行的任務。 一般來說,這裡的阻塞佇列有以下幾種選擇:
ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue
threadFactory: 執行緒工廠,主要用來建立執行緒;
handler: 表示當拒絕處理任務時的策略,有以下四種取值:
ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:只要執行緒池不關閉,該策略直接在呼叫者執行緒中,運行當前被丟棄的任務
以下主要講解儲存等待執行的任務的佇列對執行緒池執行的影響。
1.有界佇列
1.初始的poolSize < corePoolSize,提交的runnable任務,會直接做為new一個Thread的引數,立馬執行。
2.當提交的任務數超過了corePoolSize,會將當前的runable提交到一個block queue中。
3.有界佇列滿了之後,如果poolSize < maximumPoolsize時,會嘗試new 一個Thread的進行救急處理,立馬執行對應的runnable任務。
4.如果3中也無法處理了,就會走到第四步執行reject操作。
public class ThreadPoolExcutorTest implements Runnable {
public String name;
public ThreadPoolExcutorTest(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(3);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
1, //corePoolSize
2, //maximumPoolSize
1L,
TimeUnit.SECONDS,
workQueue
);
threadPool.execute(new ThreadPoolExcutorTest("任務1"));
threadPool.execute(new ThreadPoolExcutorTest("任務2"));
threadPool.execute(new ThreadPoolExcutorTest("任務3"));
threadPool.execute(new ThreadPoolExcutorTest("任務4"));
threadPool.execute(new ThreadPoolExcutorTest("任務5"));
threadPool.execute(new ThreadPoolExcutorTest("任務6"));
threadPool.shutdown();
}
}
分析:執行緒池的corePoolSize為1,任務1提交後,執行緒開始執行,corePoolSize 數量用完,接著任務2、3、4提交,放到了有界佇列中,此時有界佇列也滿了。繼續提交任務5,由於當前執行的執行緒數poolSize < maximumPoolsize,執行緒池嘗試new一個新的執行緒來執行任務5,所以任務5會接著執行。當繼續提交任務6,時,poolSize達到了maximumPoolSize,有界佇列也滿了,所以執行緒池執行了拒絕操作。
2.無界佇列
與有界佇列相比,除非系統資源耗盡,否則無界的任務佇列不存在任務入隊失敗的情況。當有新的任務到來,系統的執行緒數小於corePoolSize時,則新建執行緒執行任務。當達到corePoolSize後,就不會繼續增加,若後續仍有新的任務加入,而沒有空閒的執行緒資源,則任務直接進入佇列等待。若任務建立和處理的速度差異很大,無界佇列會保持快速增長,直到耗盡系統記憶體。
public class ThreadPoolExcutorTest2 implements Runnable {
public Integer count;
public ThreadPoolExcutorTest2(Integer count) {
this.count = count;
}
@Override
public void run() {
System.out.println("任務" + count);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 1L, TimeUnit.SECONDS, workQueue);
for (int i = 1; i <= 20; i++) {
pool.execute(new ThreadPoolExcutorTest2(i));
}
Thread.sleep(1000);
System.out.println("執行緒池中佇列中的執行緒數量:" + workQueue.size());
pool.shutdown();
}
}
如果修改了執行緒池的maximumPoolSize引數(大於corePoolSize的大小),程式執行結果不受影響。所以對於無界佇列,maximumPoolSize的設定設定的再大對於執行緒的執行是沒有影響的。
Ps:這裡說LinkedBlockingQueue是無界佇列是不恰當的,只不過如果用無參建構函式初始化,預設的容量是Integer.MAX_VALUE
總結
當執行緒池的任務快取佇列已滿並且執行緒池中的執行緒數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略。