一、執行緒池什麼時候用,有什麼好處?
“執行緒池”顧名思義,就是存放執行緒的池子,這個池子可以存放多少執行緒取決於採用哪種執行緒池,取決於有多少併發執行緒,有多少計算機的硬體資源。使用執行緒池最直接的好處就是:執行緒可以重複利用、減少建立和銷燬執行緒所帶來的系統資源的開銷,提升效能(節省執行緒建立的時間開銷,使程式響應更快)。
二、JDK自帶4種的執行緒池(JDK1.5之後)
2.1、固定執行緒數的執行緒池(newFixedThreadPool)
這種執行緒池裡面的執行緒被設計成存放固定數量的執行緒,具體執行緒數可以考慮為CPU核數*N(N可大可小,取決於併發的執行緒數,計算機可用的硬體資源等)。可以通過下面的程式碼來獲取當前計算機的CPU的核數。
int processors = Runtime.getRuntime().availableProcessors();
FixedThreadPool 是通過 java.util.concurrent.Executors 建立的 ThreadPoolExecutor 例項。這個例項會複用 固定數量的執行緒處理一個共享的無邊界佇列 。任何時間點,最多有 nThreads 個執行緒會處於活動狀態執行任務。如果當所有執行緒都是活動時,有多的任務被提交過來,那麼它會一致在佇列中等待直到有執行緒可用。如果任何執行緒在執行過程中因為錯誤而中止,新的執行緒會替代它的位置來執行後續的任務。所有執行緒都會一致存於執行緒池中,直到顯式的執行 ExecutorService.shutdown() 關閉。由於阻塞佇列使用了LinkedBlockingQueue,是一個無界佇列,因此永遠不可能拒絕任務。LinkedBlockingQueue在入佇列和出佇列時使用的是不同的Lock,意味著他們之間不存在互斥關係,在多CPU情況下,他們能正在在同一時刻既消費,又生產,真正做到並行。因此這種執行緒池不會拒絕任務,而且不會開闢新的執行緒,也不會因為執行緒的長時間不使用而銷燬執行緒。這是典型的生產者----消費者問題,這種執行緒池適合用在穩定且固定的併發場景,比如伺服器。下面程式碼給出一個固定執行緒數的DEMO,每個核繫結了5個執行緒。
import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { // 獲取計算機有幾個核 int processors = Runtime.getRuntime().availableProcessors(); // 第一種執行緒池:固定個數的執行緒池,可以為每個CPU核繫結一定數量的執行緒數 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(processors * 5); for (int i = 0; i < 10; i++) { fixedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } fixedThreadPool.shutdown(); } }
2.2、快取的執行緒池(newCachedThreadPool)
核心池大小為0,執行緒池最大執行緒數目為最大整型,這意味著所有的任務一提交就會加入到阻塞佇列中。當執行緒池中的執行緒60s沒有執行任務就終止,阻塞佇列為SynchronousQueue。SynchronousQueue的take操作需要put操作等待,put操作需要take操作等待,否則會阻塞(執行緒池的阻塞佇列不能儲存,所以當目前執行緒處理忙碌狀態時,所以開闢新的執行緒來處理請求),執行緒進入wait set。總結下來:①這是一個可以無限擴大的執行緒池;②適合處理執行時間比較小的任務;③執行緒空閒時間超過60s就會被殺死,所以長時間處於空閒狀態的時候,這種執行緒池幾乎不佔用資源;④阻塞佇列沒有儲存空間,只要請求到來,就必須找到一條空閒執行緒去處理這個請求,找不到則線上程池新開闢一條執行緒。如果主執行緒提交任務的速度遠遠大於CachedThreadPool的處理速度,則CachedThreadPool會不斷地建立新執行緒來執行任務,這樣有可能會導致系統耗盡CPU和記憶體資源,所以在使用該執行緒池是,一定要注意控制併發的任務數,否則建立大量的執行緒可能導致嚴重的效能問題。
import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { // 快取執行緒池,無上限 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 100; i++) { cachedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } cachedThreadPool.shutdown(); } }
2.3、單個執行緒的執行緒池(newSingleThreadExecutor)
SingleThreadExecutor是使用單個worker執行緒的Executor,作為單一worker執行緒的執行緒池,SingleThreadExecutor把corePool和maximumPoolSize均被設定為1,和FixedThreadPool一樣使用的是無界佇列LinkedBlockingQueue,所以帶來的影響和FixedThreadPool一樣。對於newSingleThreadExecutor()來說,也是當執行緒執行時丟擲異常的時候會有新的執行緒加入執行緒池替他完成接下來的任務。建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行,所以這個比較適合那些需要按序執行任務的場景。比如:一些不太重要的收尾,日誌等工作可以放到單執行緒的執行緒中去執行。日誌記錄一般情況會比較慢(資料量大一般可能不寫入資料庫),順序執行會拖慢整個介面,堆積更多請求,還可能會對資料庫造成影響(事務在開啟中),所以日誌記錄完全可以扔到單執行緒的執行緒中去,一條條的處理,也可以認為是一個單消費者的生產者消費者模式。
import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { public static void main(String[] args) { // 單一執行緒池,永遠會維護存在一條執行緒 ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int j = i; singleThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":" + j); } }); } singleThreadPool.shutdown(); } }
2.4、固定個數的執行緒池(newScheduledThreadPool)
相比於第一個固定個數的執行緒池強大在 ①可以執行延時任務,②也可以執行帶有返回值的任務
import java.util.concurrent.*; public class Test { public static void main(String[] args) throws InterruptedException, ExecutionException{ // 第四種執行緒池:固定個數的執行緒池,相比於第一個固定個數的執行緒池 強大在 ①可以執行延時任務,②也可以執行 // 有返回值的任務。 // scheduledThreadPool.submit(); 執行帶有返回值的任務 // scheduledThreadPool.schedule() 用來執行延時任務. // 固定個數的執行緒池,可以執行延時任務,也可以執行帶有返回值的任務。 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); FutureTask<String> ft = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { System.out.println("hello"); return Thread.currentThread().getName(); } }); scheduledThreadPool.submit(ft); // 通過FutureTask物件獲得返回值. String result = ft.get(); System.out.println("result : " + result); // 執行延時任務 scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " : bobm!"); } }, 3L, TimeUnit.SECONDS); } }