實際開發(fā)中需要開啟異步線程時攒庵,我們都會使用ThreadPoolExecutor類嘴纺。但是我們一般不會直接通過Executors來隨便定義出一個線程池,而是抽象一個utils單例的保存線程池對象供項目使用浓冒,根據業(yè)務不同栽渴,可能有多個。
private static ThreadPoolExecutor exec = new ThreadPoolExecutor(10, 30,
300, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10000),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
這是一個線程池的配置稳懒,各個參數含義如下:
1闲擦、corePoolSize, 核心線程數,建議和cpu的核心數差不多场梆,當有任務提交墅冷,檢測當前線程池內的線程數小于corePoolSize的話,新建線程執(zhí)行任務或油,而不會開始復用寞忿,會新建,直到達到corePoolSize顶岸。線程池內的線程數大于等于corePoolSize時腔彰,將任務放入workQueue等待。
2辖佣、maximumPoolSize霹抛,允許線程池內最大線程數,當隊列滿了之后卷谈,如果線程池內的線程數小于maximumPoolSize新建線程杯拐,如果大于等于執(zhí)行拒絕策略。
如果maximumPoolSize是30,corePoolSize是10藕施,當隊列滿了后只能再開20個線程寇损。
3、keepAliveTime裳食,線程池空閑時矛市,線程存活的時間。
4诲祸、workQueue浊吏,上面提到的線程數超過corePoolSize存放任務的地方。
5救氯、threadFactory找田,線程工廠,可以自己重寫一下着憨,為每個線程賦予一個名字墩衙,便于排查問題。
6甲抖、handler漆改,拒絕策略,分4種准谚,AbortPolicy直接拋出異常挫剑、DiscardPolicy悄悄拋棄不執(zhí)行、CallerRunsPolicy(調用者運行):該策略既不會拋棄任務也不會拋出異常柱衔,而是將這個任務退回給調用者樊破,從而降低新任務的流量;唆铐、DiscardOldestPolicy(拋棄最舊的)
調用時我們會封裝個方法調用到exec.execute方法
public void execute(Runnable task) {
exec.execute(task);
}
提交后大致的流程:
1哲戚、通過調用execute方法,調用ThreadPoolExecutor的addWork方法艾岂,Work類是對需要執(zhí)行的task的封裝惫恼,task保存在Work的firstTask變量中。execute方法中會做出將任務放在那里的判斷澳盐,如果進隊列,會將任務入隊令宿,并且addWord的task參數為null叼耙。
2、addWork方法中粒没,會初始化一個Work對象筛婉,構造方法中將任務傳入,賦值給firstTask變量,并從ThreadFactory中新建一個線程參數是this賦值給Work的thread變量爽撒。
3入蛆、然后后面調用Work中的thread變量的start方法,這樣調用到了Work的run方法硕勿。
4哨毁、work的run方法調用runWorker方法傳入this參數,方法內拿出work的firstTask賦值給task變量源武,如果為null,從隊列里面拿话浇,然后調用task的run方法執(zhí)行任務。
注意到在runwork中,task的run方法之前需要獲得鎖蹋订,為了防止線程池shutdown等操作中斷任務,所以加鎖。并且是不可重入鎖荠锭。Work類通過繼承AbstractQueuedSynchronizer實現的不可重入鎖证九。(可重入鎖的意思是在同一線程妈拌,再次獲取相同鎖資源不用獲取直接用猜惋,主要目的防止死鎖缓窜,synchronize和ReentrantLock都是可重入鎖。)
這里使用不可重入鎖的目的是防止beforeExecute等自定義方法調用setCorePoolSize方法進一步調用到interruptIdleWorkers里面的trylock獲取鎖成功導致自己將自己中斷
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
t.interrupt();會將自己中斷掉港粱。