你真的了解線程池的七個參數(shù)是做什么的嗎?
問:可以說一下線程池嗎?
關于線程池的問題,大多數(shù)面試官會問線程池的幾個參數(shù)的含義,今天就直接聊一聊線程池ThreadPoolExecutor。
先說下線程池中幾個參數(shù)的含義:
ThreadPoolExecutor初始化的時候一般會有7個參數(shù):
- corePoolSize:核心線程數(shù)
- maximumPoolSize:最大線程數(shù)
- keepAliveTime:非核心線程?;顣r間
- unit:單位
- workQueue:隊列
- Executors.defaultThreadFactory():線程工場
- 拒絕策略
ThreadPoolExecutor的工作原理:
往線程池中提交第一個任務,底層會創(chuàng)建第一個核心線程,將線程和任務封裝為一個woker對象放入set集合中,接下來每提交一個的任務都會對應創(chuàng)建一個核心線程和這個任務封裝的woker對象放入set集合中,直到核心線程數(shù)達到corePoolSize,再次提交到線程池的任務會被放到阻塞隊列排隊執(zhí)行,如果放隊列的過程中,隊列滿了,就會創(chuàng)建一個非核心線程和這個任務封裝為woker對象放入set集合中,如果最終已經(jīng)達到最大線程數(shù)maximumPoolSize,就采用拒絕策略。如果放入隊列過程中發(fā)現(xiàn)工作線程數(shù)位0,則創(chuàng)建一個空任務的Worker。
再來看下線程池的標識:
線程池的標識有兩層含義:
- 一個含義是當前線程池中的線程數(shù)量;
- 一個含義是當前線程池的狀態(tài);
底層是用按位分隔的設計方式將一個int類型的變量的32位進行分割,用高3位表示線程狀態(tài),低29位表示線程數(shù)量。
線程池的5個狀態(tài):RUNNING= -1,正常運行狀態(tài) SHUTDOWN= 0, 表示不接受新任務,只把隊列中的任務處理完結(jié)束。STOP= 1,表示不接受新任務,也不處理隊列中的任務了。IDYING= 2,非正常狀態(tài) TERMINATED = 3,死亡狀態(tài)
按位分割的好處就是用一個變量表示兩個狀態(tài),在修改的時候可以利用cas保證原子性。
Worker對象創(chuàng)建邏輯是由addWorker方法實現(xiàn)的。
addWorker方法邏輯:
retry;雙層循環(huán)
第一層循環(huán)主要判斷:如果當前線程池狀態(tài)為RUNNING就放行, 如果狀態(tài)為SHUTDOWN就必須滿足傳進來的新任務為null,隊列中有待處理的任務才會放行(因為SHUTDOWN狀態(tài)下不接受新任務,只處理隊列中的任務);如果狀態(tài)為STOP,IDYING,TERMINATED就一定不放行;
第二層循環(huán)主要是判斷線程數(shù),如果是創(chuàng)建核心線程,就判斷是否達到corePoolSize,否則就判斷是否達到maximumPoolSize,如果達到就返回fasle不放行。
如果未達到就放行,放行的時候會利用cas更新線程數(shù),如果更新成功則兩層循環(huán)結(jié)束,繼續(xù)下面的邏輯。
因為是cas操作,多線程的情況下可能會有更新線程數(shù)量失敗的情況,在這種情況下要判斷之前獲取的線程池狀態(tài)和現(xiàn)在的線程池狀態(tài)是否一致,如果不一致那就要重新判斷狀態(tài),從而進入到外層循環(huán)的下一輪循環(huán),如果一致就只需要進入到內(nèi)層循環(huán)的下一輪循環(huán)。
創(chuàng)建Worker對象
接下來就是創(chuàng)建Worker(任務),Worker類繼承aqs,封裝了線程工廠,初始化的時候會利用工廠創(chuàng)建一個線程,并且和傳進來的任務封裝為worker對象。
獲取線程池全局鎖(reentrylock作為線程池全局鎖),進行上鎖操作
將創(chuàng)建的worker加入workers集合,workers是一個hashset集合。
放入集合后就可以解鎖了
worker創(chuàng)建完成了,接下來就是啟動線程,啟動線程后就會執(zhí)行worker中的run方法
run方法流程
這個方法中主要的邏輯是這段代碼
while (task != null || (task = getTask()) != null){
邏輯
}
task是worker對象封裝的任務。如果當前worker對象上沒有任務就調(diào)用getTask去阻塞隊列拿任務,如果能拿到就處理任務。如果getTask返回null就跳出循環(huán),進入processWorkerExit方法。
我們知道線程池中的任務是放在隊列中的,ThreadPoolExecutor中的隊列一般默認是阻塞隊列LinkedBlockingQueue,
getTask()方法會在這個隊列中拿任務,如果有任務就直接返回任務,如果此時隊列中無任務,當前線程會阻塞等待任務到來。
但是如果設置了非核心線程最大空閑時間keepAliveTime,代表非核心線程的worker對象中的線程在拿任務的時候不會用take方法,而是用poll,poll這個方法可以設置阻塞等待時間為keepAliveTime。當超過這個時間還沒有任務就會返回null。
- processWorkerExit方法邏輯
上一步中如果沒有獲取到任務并且返回了null就會進入processWorkerExit方法。這個方法的邏輯就是把當前非核心線程的worker從workers集合中移除。最后會做一個判斷:如果此時沒有任何工作線程了,并且阻塞隊列中還有任務,那就再創(chuàng)建一個不帶任務的非核心線程worker。保證有線程去處理隊列中的任務。
拒絕策略:
- AbortPolicy(默認):丟棄任務并拋出 RejectedExecutionException 異常。
- CallerRunsPolicy:由調(diào)用線程處理該任務。
- DiscardPolicy:丟棄任務,但是不拋出異常??梢耘浜线@種模式進行自定義的處理方式。
- DiscardOldestPolicy:丟棄隊列最早的未處理任務,然后重新嘗試執(zhí)行任務。
其他了解:
線程監(jiān)控API:
while (true) {
System.out.println();
int queueSize = tpe.getQueue().size();
System.out.println("當前排隊線程數(shù):" + queueSize);
int activeCount = tpe.getActiveCount();
System.out.println("當前活動線程數(shù):" + activeCount);
long completedTaskCount = tpe.getCompletedTaskCount();
System.out.println("執(zhí)行完成線程數(shù):" + completedTaskCount);
long taskCount = tpe.getTaskCount();
System.out.println("總線程數(shù):" + taskCount);
Thread.sleep(3000);
}