一、線程池什么時候用,有什么好處?
“線程池”顧名思義,就是存放線程的池子,這個池子可以存放多少線程取決於采用哪種線程池,取決於有多少並發線程,有多少計算機的硬件資源。使用線程池最直接的好處就是:線程可以重復利用、減少創建和銷毀線程所帶來的系統資源的開銷,提升性能(節省線程創建的時間開銷,使程序響應更快)。
二、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); } }