java线程不执行_java线程池,阿里为什么不允许使用Executors?
帶著問題
阿里Java代碼規(guī)范為什么不允許使用Executors快速創(chuàng)建線程池?
下面的代碼輸出是什么?
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, //corePoolSize
100, //maximumPoolSize
100, //keepAliveTime
TimeUnit.SECONDS, //unit
new LinkedBlockingDeque<>(100));//workQueue
for (int i = 0; i < 5; i++) {
final int taskIndex = i;
executor.execute(() -> {
System.out.println(taskIndex);
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
A) 0 1 2 3 4 5
B) 0~5 順序不一致輸出5行
C) 0
基礎(chǔ)
什么是線程池?
線程池可以通過池看出來是一個(gè)資源集,任何池的作用都大同小異,主要是用來減少資源創(chuàng)建、初始化的系統(tǒng)開銷。
創(chuàng)建線程很“貴”嗎?
是的。創(chuàng)建線程的代價(jià)是昂貴的。
我們都知道系統(tǒng)中的每個(gè)進(jìn)程有自己獨(dú)立的內(nèi)存空間,而被稱為輕量級(jí)進(jìn)程的線程也是需要的。
在JVM中默認(rèn)一個(gè)線程需要使用256k~1M(取決于32位還是64位操作系統(tǒng))的內(nèi)存。(具體的數(shù)組我們不深究,因?yàn)殡S著JVM版本的變化這個(gè)默認(rèn)值隨時(shí)可能發(fā)生變更,我們只需要知道線程是需要占用內(nèi)存的)
除了內(nèi)存還有更多嗎?
許多文章會(huì)將上下文切換、CPU調(diào)度列入其中,這邊不將線程調(diào)度列入是因?yàn)樗咧械木€程不會(huì)被調(diào)度(OS控制),如果不是睡眠中的線程那么是一定需要被調(diào)度的。
但在JVM中除了創(chuàng)建時(shí)的內(nèi)存消耗,還會(huì)給GC帶來壓力,如果頻繁創(chuàng)建線程那么相對(duì)的GC的時(shí)候也需要回收對(duì)應(yīng)的線程。
線程池的機(jī)制?
可以看到線程池是一種重復(fù)利用線程的技術(shù),線程池的主要機(jī)制就是保留一定的線程數(shù)在沒有事情做的時(shí)候使之睡眠,當(dāng)有活干的時(shí)候拿一個(gè)線程去運(yùn)行。
這些牽扯到線程池實(shí)現(xiàn)的具體策略。
還有哪些常見的池?
線程池
連接池(數(shù)據(jù)庫連接、TCP連接等)
BufferPool
......
Java中的線程池
UML圖(Java 8)
可以看到真正的實(shí)現(xiàn)類有
ThreadPoolExecutor (1.5)
ForkJoinPool (1.7)
ScheduledThreadPoolExecutor (1.5)
今天我們主要談?wù)?ThreadPoolExecutor 也是使用率較高的一個(gè)實(shí)現(xiàn)。
Executors提供的工廠方法
newCachedThreadPool (ThreadPoolExecutor)
創(chuàng)建一個(gè)可緩存的線程池。如果線程池的大小超過了處理任務(wù)所需要的線程,那么就會(huì)回收部分空閑(60秒不執(zhí)行任務(wù))的線程,當(dāng)任務(wù)數(shù)增加時(shí),此線程池又可以智能的添加新線程來處理任務(wù)。此線程池不會(huì)對(duì)線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。
newFixedThreadPool (ThreadPoolExecutor)
創(chuàng)建固定大小的線程池。每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到線程達(dá)到線程池的最大大小。線程池的大小一旦達(dá)到最大值就會(huì)保持不變,如果某個(gè)線程因?yàn)閳?zhí)行異常而結(jié)束,那么線程池會(huì)補(bǔ)充一個(gè)新線程。
newSingleThreadExecutor (ThreadPoolExecutor)
創(chuàng)建一個(gè)單線程的線程池。這個(gè)線程池只有一個(gè)線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)。如果這個(gè)唯一的線程因?yàn)楫惓=Y(jié)束,那么會(huì)有一個(gè)新的線程來替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。
newScheduledThreadPool (ScheduledThreadPoolExecutor)
創(chuàng)建一個(gè)大小無限的線程池。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。
newSingleThreadScheduledExecutor (ScheduledThreadPoolExecutor)
創(chuàng)建一個(gè)單線程用于定時(shí)以及周期性執(zhí)行任務(wù)的需求。
newWorkStealingPool (1.8 ForkJoinPool)
創(chuàng)建一個(gè)工作竊取
可以看到各種不同的工廠方法中使用的線程池實(shí)現(xiàn)類最終只有3個(gè),對(duì)應(yīng)關(guān)系如下:
工廠方法
實(shí)現(xiàn)類
newCachedThreadPool
ThreadPoolExecutor
newFixedThreadPool
ThreadPoolExecutor
newSingleThreadExecutor
ThreadPoolExecutor
newScheduledThreadPool
ScheduledThreadPoolExecutor
newSingleThreadScheduledExecutor
ScheduledThreadPoolExecutor
newWorkStealingPool
ForkJoinPool
ThreadPoolExecutor
首先我們看下 ThreadPoolExecutor 的完全構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心池大小,除非設(shè)置了 allowCoreThreadTimeOut 否則哪怕線程超過空閑時(shí)間,池中也要最少要保留這個(gè)數(shù)目的線程。
需要注意的是,corePoolSize所需的線程并不是立即創(chuàng)建的,需要在提交任務(wù)之后進(jìn)行創(chuàng)建,所以如果有大量的緩存線程數(shù)可以先提交一個(gè)空任務(wù)讓線程池將線程先創(chuàng)建出來,從而提升后續(xù)的執(zhí)行效率。
maximumPoolSize
允許的最大線程數(shù)。
keepAliveTime
空閑線程空閑存活時(shí)間,核心線程需要 allowCoreThreadTimeOut 為true才會(huì)退出。
unit
與 keepAliveTime 配合,設(shè)置 keepAliveTime 的單位,如:毫秒、秒。
workQueue
線程池中的任務(wù)隊(duì)列。上面提到線程池的主要作用是復(fù)用線程來處理任務(wù),所以我們需要一個(gè)隊(duì)列來存放需要執(zhí)行的任務(wù),在使用池中的線程來處理這些任務(wù),所以我們需要一個(gè)任務(wù)隊(duì)列。
threadFactory
當(dāng)線程池判斷需要新的線程時(shí)通過線程工程創(chuàng)建線程。
handler
執(zhí)行被阻止時(shí)的處理程序,線程池?zé)o法處理。這個(gè)與任務(wù)隊(duì)列相關(guān),比如隊(duì)列中可以指定隊(duì)列大小,如果超過了這個(gè)大小該怎么辦呢?JDK已經(jīng)為我們考慮到了,并提供了4個(gè)默認(rèn)實(shí)現(xiàn)。
下列是JDK中默認(rèn)攜帶的策略:
AbortPolicy (默認(rèn))
拋出 RejectedExecutionException 異常。
CallerRunsPolicy
調(diào)用當(dāng)前線程池所在的線程去執(zhí)行。
DiscardPolicy
直接丟棄當(dāng)前任務(wù)。
DiscardOldestPolicy
將最舊的任務(wù)丟棄,將當(dāng)前任務(wù)添加到隊(duì)列。
容易混淆的參數(shù):corePoolSize maximumPoolSize workQueue
任務(wù)隊(duì)列、核心線程數(shù)、最大線程數(shù)的邏輯關(guān)系
當(dāng)線程數(shù)小于核心線程數(shù)時(shí),創(chuàng)建線程。
當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列未滿時(shí),將任務(wù)放入任務(wù)隊(duì)列。
當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列已滿
若線程數(shù)小于最大線程數(shù),創(chuàng)建線程
若線程數(shù)等于最大線程數(shù),調(diào)用拒絕執(zhí)行處理程序(默認(rèn)效果為:拋出異常,拒絕任務(wù))
那么這三個(gè)參數(shù)推薦如何設(shè)置,有最優(yōu)值嗎?
由于java對(duì)于協(xié)程的支持不友好,所以會(huì)大量依賴于線程池和線程。
從而這個(gè)值沒有最優(yōu)推薦,需要根據(jù)業(yè)務(wù)需求情況來進(jìn)行設(shè)置。
不同的需求類型可以創(chuàng)建多個(gè)不同的線程池來執(zhí)行。
問題1:阿里開發(fā)規(guī)范為什么不允許Executors快速創(chuàng)建線程池?
可以看到原因很簡(jiǎn)單
newSingleThreadExecutor
newFixedThreadPool
在 workQueue 參數(shù)直接 使用了 new LinkedBlockingQueue() 理論上可以無限添加任務(wù)到線程池。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue();
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
如果提交到線程池的任務(wù)由問題,比如 sleep 永久,會(huì)造成內(nèi)存泄漏,最終導(dǎo)致OOM。
同時(shí) 阿里還推薦自定義 threadFactory 設(shè)置線程名稱便于以后排查問題。
問題2:下面的代碼輸出是什么?
應(yīng)該選C。
雖然最大線程數(shù)有100但核心線程數(shù)為1,任務(wù)隊(duì)列由100。
滿足了 '當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列未滿時(shí),將任務(wù)放入任務(wù)隊(duì)列。' 這個(gè)條件。
所以后續(xù)添加的任務(wù)都會(huì)被堵塞。
最后
關(guān)于 ThreadPoolExecutor 的邏輯在實(shí)際使用的時(shí)候會(huì)有點(diǎn)奇怪,因?yàn)榫€程池中的線程并沒有超過最大線程數(shù),有沒有一種可能當(dāng)任務(wù)被堵塞很久的時(shí)候創(chuàng)建新的線程池來處理呢?
這邊推薦大家使用 newWorkStealingPool,也就是ForkJoinPool。采取了工作竊取的模式。
后續(xù)會(huì)跟大家一起聊聊 ForkJoinPool。
總結(jié)
以上是生活随笔為你收集整理的java线程不执行_java线程池,阿里为什么不允许使用Executors?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么设置微信红包来了自动提醒
- 下一篇: java 扫描所有子类_java获取全部