1.線程池的用法不作介紹,本文直接調試jdk7線程池源碼,來介紹執行具體流程。
客戶端代碼
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(new Runnable() {
@Override
public void run() {
System.err.println("業務執行");
}
});
}
進入Executors工具類的 newFixedThreadPool 方法來構造線程池。
這裏new了一個ThreadPoolExecutor類(線程池核心實現類),nThreads值爲1。接下來:
構造函數,構造之後裏面字段的值如下(畫紅色的需要關注下):
- corePoolSize:核心線程數線程數定義了最小可以同時運行的線程數量。
- maximumPoolSize:當隊列中存放的任務達到隊列容量的時候,當前可以同時運行的線程數量變爲最大線程數。
- keepAliveTime:當線程池中的線程數量大於 corePoolSize 的時候,如果這時沒有新的任務提交,核心線程外的線程不會立即銷燬,而是會等待,直到等待的時間超過了 keepAliveTime纔會被回收銷燬;
- workQueue:當新任務來的時候會先判斷當前運行的線程數量是否達到核心線程數,如果達到的話,任務就會被存放在阻塞隊列中。
- handler:拒絕處理任務類(默認:AbortPolicy 會拋異常)。
- threadFactory:線程工廠(默認:DefaultThreadFactory)。
接下來我們看下execute方法的鏈路執行情況
斷點可知進入addWorker方法:
線程池幾個狀態:
- RUNNING:可以接受新的任務,也可以處理阻塞隊列裏的任務。
- SHUTDOWN:不接受新的任務,但是可以處理阻塞隊列裏的任務。
- STOP:不接受新的任務,不處理阻塞隊列裏的任務,中斷正在處理的任務。
- TIDYING:過渡狀態,也就是說所有的任務都執行完了,當前線程池已經沒有有效的線程,這個時候線程池的狀態將會TIDYING,並且將要調用terminated方法。
- TERMINATED:終止狀態。terminated方法調用完成以後的狀態。
對應的代碼如下,注意他們的值是越來越大。
接着執行下半段
主要是幹了上面三件事,然後執行Worker的run方法。
由於task不爲空,第一次循環執行業務,並把task置爲null,進入第二次循環,執行getTask方法:
getTask主要是從阻塞隊列中取任務,然後返回task。
我們瞭解下阻塞隊列的poll和take方法的區別:
poll(time):取走BlockingQueue裏排在首位的對象,若不能立即取出,則可以等time參數規定的時間,取不到時返回null。
take():取走BlockingQueue裏排在首位的對象,若BlockingQueue爲空,阻斷進入等待狀態直到Blocking有新的對象被加入爲止。
到此我們的execute方法調式完畢!!!!!!
面試問題:
1.corePoolSize作用?
下面看下官方說法:
corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啓動所有基本線程。
2.keepAliveTime的作用?
keepAliveTime(線程活動保持時間):線程池的工作線程空閒後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。
3.maximumPoolSize的作用?
maximumPoolSize(線程池最大大小):線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。
4.excute的大致流程?
- 線程數量未達到corePoolSize,則新建一個線程(核心線程)執行任務
- 線程數量達到了corePools,則將任務移入隊列等待
- 隊列已滿,新建線程(非核心線程)執行任務
- 隊列已滿,總線程數又達到了maximumPoolSize,就會由(RejectedExecutionHandler)拋出異常
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//當前的Worker的數量小於核心線程池大小時,新建一個Worker。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果當前CorePool內的線程大於等於CorePoolSize,那麼將線程加入到BlockingQueue。
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))//recheck防止線程池狀態的突變,如果突變,那麼將reject線程,防止workQueue中增加新線程
reject(command);
else if (workerCountOf(recheck) == 0)//上下兩個操作都有addWorker的操作,但是如果在workQueue.offer的時候Worker變爲0,
//那麼將沒有Worker執行新的task,所以增加一個Worker. addWorker(null, false);
}
//如果workQueue滿了,那麼這時候可能還沒到線程池的maxnum,所以嘗試增加一個Worker
else if (!addWorker(command, false))
reject(command);//如果Worker數量到達上限,那麼就拒絕此線程
}
5.四種拒絕策略?
- AbortPolicy:不執行新任務,直接拋出異常,提示線程池已滿,線程池默認策略。
- DiscardPolicy:不執行新任務,也不拋出異常,基本上爲靜默模式。
- DisCardOldSetPolicy:將消息隊列中的第一個任務替換爲當前新進來的任務執行。
- CallerRunPolicy:用於被拒絕任務的處理程序,它直接在 execute 方法的調用線程中運行被拒絕的任務;如果執行程序已關閉,則會丟棄該任務。
6.Executors提供四種線程池類型?
-
CachedThreadPool():可緩存線程池。線程數無限制 有空閒線程則複用空閒線程,若無空閒線程則新建線程,一定程序減少頻繁創建/銷燬線程,減少系統開銷。
-
FixedThreadPool():定長線程池。可控制線程最大併發數(同時執行的線程數),超出的線程會在隊列中等待。
-
ScheduledThreadPool():定時線程池。支持定時及週期性任務執行。
-
SingleThreadExecutor():單線程化的線程池。有且僅有一個工作線程執行任務,所有任務按照指定順序執行,即遵循隊列的入隊出隊規則。
7.線程池容易OOM的問題?
1、newFixedThreadPool和newSingleThreadExecutor 這兩類線程池容易造成OOM。原因是構造了一個容量爲Integer.MAX_VALUE的阻塞隊列。
2、newCachedThreadPool和newScheduledThreadPool
他們的最大線程數是Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至OOM。