Java項目 當想讓程序異步操作的時候,首先考慮使用Java多線程,但有的時候我們總會在想是簡單的extends Thread 、implements Runnable接口還是使用線程池呢?而大多開發者可能更會選擇使用線程池,.減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。
2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
最近為什麼要研究這個?首先 前一段時間 公司使用dubbo服務,同事說dubbo報了一個錯是"rejectionExecution ThreadPool exhausted", 很明顯dubbo使用固定大小的線程池,後來增加線程池大小,優化業務代碼。
Java裡面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。
比較重要的幾個類:
ExecutorService
真正的線程池接口。
ScheduledExecutorService
能和Timer/TimerTask類似,解決那些需要任務重復執行的問題。
ThreadPoolExecutor
ExecutorService的默認實現。
ScheduledThreadPoolExecutor
繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,周期性任務調度的類實現。
要配置一個線程池是比較復雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在Executors類裡面提供了一些靜態工廠,生成一些常用的線程池。
1. newSingleThreadExecutor
創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
2.newFixedThreadPool
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。
3. newCachedThreadPool
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,
那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
4.newScheduledThreadPool
創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。
TestCachedThreadPool demo:
publicclass TestCachedThreadPool {
publicstaticvoid main(String[] args) {
//創建一個可重用固定線程數的線程池
ExecutorService pool = Executors.newCachedThreadPool();
//創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
//將線程放入池中進行執行
pool.execute(t1);
pool.execute(t2);
//關閉線程池
pool.shutdown();
}
}
TestScheduledThreadPoolExecutor demo:
publicclass TestScheduledThreadPoolExecutor {
publicstaticvoid main(String[] args) {
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
exec.scheduleAtFixedRate(new Runnable() {//每隔一段時間就觸發異常
@Override
publicvoid run() {
System.out.println("TestScheduledThreadPoolExecutor-->>run() error");
}
}, 1000, 5000, TimeUnit.MILLISECONDS);
exec.scheduleAtFixedRate(new Runnable() {//每隔一段時間打印系統時間,證明兩者是互不影響的
@Override
publicvoid run() {
System.out.println(System.nanoTime());
}
}, 1000, 2000, TimeUnit.MILLISECONDS);
}
}
corePoolSize - 池中所保存的線程數,包括空閒線程。
maximumPoolSize-池中允許的最大線程數。
keepAliveTime - 當線程數大於核心時,此為終止前多余的空閒線程等待新任務的最長時間。
unit - keepAliveTime 參數的時間單位。
workQueue - 執行前用於保持任務的隊列。此隊列僅保持由 execute方法提交的 Runnable任務。
threadFactory - 執行程序創建新線程時使用的工廠。
handler - 由於超出線程范圍和隊列容量而使執行被阻塞時所使用的處理程序。
ThreadPoolExecutor是Executors類的底層實現。
固定線程池大小:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
單線程:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
無界線程池,可以進行自動線程回收:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
首先是無界的線程池,所以我們可以發現maximumPoolSize為65535。其次BlockingQueue的選擇上使用SynchronousQueue。可能對於該BlockingQueue有些陌生,簡單說:該QUEUE中,每個插入操作必須等待另一個線程的對應移除操作。
先從BlockingQueue<Runnable> workQueue這個入參開始說起。在JDK中,其實已經說得很清楚了,一共有三種類型的queue。
所有BlockingQueue 都可用於傳輸和保持提交的任務。可以使用此隊列與池大小進行交互:
如果運行的線程少於 corePoolSize,則 Executor始終首選添加新的線程,而不進行排隊。(如果當前運行的線程小於corePoolSize,則任務根本不會存放,添加到queue中,而是直接抄家伙(thread)開始運行)
如果運行的線程等於或多於 corePoolSize,則 Executor始終首選將請求加入隊列,而不添加新的線程。
如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。
queue上的三種類型。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-01/111467p2.htm