java多線程中,線程池的最上層接口是Executor,ExecutorService實現了Executor,是真正的管理線程池的接口,ThreadPoolExecutor間接繼承了ExecutorService,提供了多種具體的線程池實現,在日常開發中一般直接使用Executors工具類提供的幾種常用ThreadPoolExecutor,下面詳細介紹下ThreadPoolExecutor.
ThreadPoolExecutor基本參數
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1000L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(20),
new ThreadPoolExecutor.CallerRunsPolicy());
corePoolSize:核心線程數的大小,也有說線程池最小線程數
maximumPoolSize:線程池最大線程數
keepAliveTime:當沒有任務執行時,線程能存活的最大時間,這里說的線程是指大於corePoolSize,小於maximumPoolSize的線程
timeunit:keepAliveTime的時間單位
workQueue:用來存放task的堵塞隊列,隊列的選擇和size的大小對線程池的運行有直接影響,默認有幾種實現,后面詳說.
rejectHandler:拒絕策略,當隊列已滿並且線程數達到maximumPoolSize時,再有新任務進來時會執行拒絕策略,默認集中實現,后面詳說.
ThreadPool模型初始化
ThreadPool線程池初始化時,不會創建corePoolSIze的線程,也就是說在沒有task進來的時候,線程池是空的,當有task進來的時候,開始創建線程,並且線程執行完task后不會銷毀,而是駐留內存,直至達到corePoolSize,那什么時候線程數會再度增加達到maxPoolSize呢,這就取決於存放task的queue的size了,如果task的數量一直不超過指定的size那么就不會創建新的線程出來,反之,則會創建新的線程去執行task,那么新建出來的線程執行完task也會一直駐留內存嗎?答案是不會,這時候就要看設置的keepAliveTime,如果在超過了這個時間后還是沒有task去使用這個線程,則線程銷毀,直至線程數等於corePoolSize.那么如果queue也滿了,線程數也達到maxPoolsize,這時候怎么辦呢,這時rejectHandler就會發揮作用,會根據我們指定的拒絕策略去處理這種場景.
Executors的幾個默認實現
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())); }
1.newSingleThreadExecutor:創建一個單線程的線程池.如果這個線程在執行霍城中因為異常結束,則會創建一個新的線程來代替它,這個線程池保證所有任務的執行順序是按照任務提交順序進行.
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); }
2.newFixedThreadPool:創建一個固定大小的線程池.看源碼可知,該實現創建了一個corePoolSize等於maximumPoolSize的線程池.
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>()); }
3.newCachedThreadPool:創建一個可緩存的線程池.看源碼可知corePoolSize=0,即當線程空閑時會被回收,當線程忙碌時又可以"源源不斷"的創建新線程來執行task.
默認線程池實現中還有ScheduledThreadPoolExecutor,可以實現定時的一些功能,可用來代替Timer或者TimeTask.這里不再展開說.
線程池的堵塞隊列
如何選擇線程池的堵塞隊列,取決我們的業務場景,即我們希望以一種什么樣的排隊策略來處理任務.排隊通常有3種策略,對應下面幾種queue.
直接提交(SynchronousQueue):不排隊,這個隊列不會存儲task,會將調用方的task直接提交給線程,指定了SynchronousQueue的線程池通常會把maximumPoolSize配置的比較大,否則可能會導致沒有足夠的線程來執行task,而導致task無法放入queue而被丟棄或拒絕.
有界隊列(ArrayBlockingQueue):通過指定ArrayBlockingQueue的size可以設置隊列的最大存儲個數,當超出這個個數時就會新建線程去執行task直至線程數達到maximumPoolSize,有界隊列size的設置和maximumPoolSize的設置息息相關.會影響CPU的使用率以及系統吞吐量.
無界隊列(不設定size的LinkedBlockingQueue):當線程數達到corePoolSize的仍有task進來時,會源源不斷進隊列,由於無解,maximumPoolSize參數會失效,線程數最大只能達到corPoolSize.
線程池拒絕策略
CallerRunsPolicy:使用調用方的線程來執行task,通常情況下調用方線程就是指我們所說的主線程,這樣的好處是不會丟棄task,但是缺點也很明顯,使用這種策略會堵塞主線程,進而拖慢主線程的整個調用時長.而基於此,該策略同時也減緩了新的task提交進來的速度(因為主線程本來只需要用來提交task就好了,現在直接去執行task,后面的task進來的速度就慢了).
AbortPolicy:這個策略簡單粗暴,直接拋出異常,不跟你多BB,需要注意的是,這個是jdk的默認策略.
DiscardPolicy:這個和AbortPolicy差不多,區別是不會拋出異常,直接丟棄.
DiscardOldestPolicy:這也是一種丟棄策略,不過和上面的DiscardPolicy剛好相反,她丟棄的不是新進來的task而是在堵塞隊列中存在時間最久的那個task,即丟棄最早進入隊列並且還沒有被執行的task.
