服務端應用程序(如數據庫和 Web 服務器)須要處理來自客戶端的高併發、耗時較短的請求任務,因此頻繁的建立處理這些請求的所須要的線程就是一個很是消耗資源的操做。常規的方法是針對一個新的請求建立一個新線程,雖然這種方法彷佛易於實現,但它有重大缺點。爲每一個請求建立新線程將花費更多的時間,在建立和銷燬線程時花費更多的系統資源。所以同時建立太多線程的 JVM 可能會致使系統內存不足,這就須要限制要建立的線程數,也就是須要使用到線程池。vue
線程池技術就是線程的重用技術,使用以前建立好的線程來執行當前任務,並提供了針對線程週期開銷和資源衝突問題的解決方案。 因爲請求到達時線程已經存在,所以消除了線程建立過程致使的延遲,使應用程序獲得更快的響應。spring
上圖表示線程池初始化具備3 個線程,任務隊列中有5 個待運行的任務對象。數據庫
執行器線程池方法編程
方法 | 描述 |
---|---|
newFixedThreadPool(int) | 建立具備固定的線程數的線程池,int參數表示線程池內線程的數量 |
newCachedThreadPool() | 建立一個可緩存線程池,該線程池可靈活回收空閒線程。若無空閒線程,則新建線程處理任務。 |
newSingleThreadExecutor() | 建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務 |
newScheduledThreadPool | 建立一個定長線程池,支持定時及週期性任務執行 |
在固定線程池的狀況下,若是執行器當前運行的全部線程,則掛起的任務將放在隊列中,並在線程變爲空閒時執行。後端
在下面的內容中,咱們將介紹線程池的executor執行器。緩存
建立線程池處理任務要遵循的步驟springboot
//第一步: 建立一個任務對象(實現Runnable接口),用於執行具體的任務邏輯 (Step 1) class Task implements Runnable { private String name; public Task(String s) { name = s; } // 打印任務名稱並Sleep 1秒 // 整個處理流程執行5次 public void run() { try{ for (int i = 0; i<=5; i++) { if (i==0) { Date d = new Date(); SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss"); System.out.println("任務初始化" + name +" = " + ft.format(d)); //第一次執行的時候,打印每個任務的名稱及初始化的時間 } else{ Date d = new Date(); SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss"); System.out.println("任務正在執行" + name +" = " + ft.format(d)); // 打印每個任務處理的執行時間 } Thread.sleep(1000); } System.out.println("任務執行完成" + name); } catch(InterruptedException e) { e.printStackTrace(); } } }
測試用例服務器
public class ThreadPoolTest { // 線程池裏面最大線程數量 static final int MAX_SIZE = 3; public static void main (String[] args) { // 建立5個任務 Runnable r1 = new Task("task 1"); Runnable r2 = new Task("task 2"); Runnable r3 = new Task("task 3"); Runnable r4 = new Task("task 4"); Runnable r5 = new Task("task 5"); // 第二步:建立一個固定線程數量的線程池,線程數爲MAX_SIZE ExecutorService pool = Executors.newFixedThreadPool(MAX_SIZE); // 第三步:將待執行的任務對象交給ExecutorService進行任務處理 pool.execute(r1); pool.execute(r2); pool.execute(r3); pool.execute(r4); pool.execute(r5); // 第四步:關閉線程池 pool.shutdown(); } }
示例執行結果多線程
任務初始化task 1 = 05:25:55 任務初始化task 2 = 05:25:55 任務初始化task 3 = 05:25:55 任務正在執行task 3 = 05:25:56 任務正在執行task 1 = 05:25:56 任務正在執行task 2 = 05:25:56 任務正在執行task 1 = 05:25:57 任務正在執行task 3 = 05:25:57 任務正在執行task 2 = 05:25:57 任務正在執行task 3 = 05:25:58 任務正在執行task 1 = 05:25:58 任務正在執行task 2 = 05:25:58 任務正在執行task 2 = 05:25:59 任務正在執行task 3 = 05:25:59 任務正在執行task 1 = 05:25:59 任務正在執行task 1 = 05:26:00 任務正在執行task 2 = 05:26:00 任務正在執行task 3 = 05:26:00 任務執行完成task 3 任務執行完成task 2 任務執行完成task 1 任務初始化task 5 = 05:26:01 任務初始化task 4 = 05:26:01 任務正在執行task 4 = 05:26:02 任務正在執行task 5 = 05:26:02 任務正在執行task 4 = 05:26:03 任務正在執行task 5 = 05:26:03 任務正在執行task 5 = 05:26:04 任務正在執行task 4 = 05:26:04 任務正在執行task 4 = 05:26:05 任務正在執行task 5 = 05:26:05 任務正在執行task 4 = 05:26:06 任務正在執行task 5 = 05:26:06 任務執行完成task 4 任務執行完成task 5
如程序執行結果中顯示的同樣,任務 4 或任務 5 僅在池中的線程變爲空閒時才執行。在此以前,額外的任務將放在待執行的隊列中。併發
線程池執行前三個任務,線程池內線程回收空出來以後再去處理執行任務 4 和 5
使用這種線程池方法的一個主要優勢是,假如您但願一次處理10000個請求,但不但願建立10000個線程,從而避免形成系統資源的過量使用致使的宕機。您可使用此方法建立一個包含500個線程的線程池,而且能夠向該線程池提交500個請求。
ThreadPool此時將建立最多500個線程,一次處理500個請求。在任何一個線程的進程完成以後,ThreadPool將在內部將第501個請求分配給該線程,並將繼續對全部剩餘的請求執行相同的操做。在系統資源比較緊張的狀況下,線程池是保證程序穩定運行的一個有效的解決方案。
線程池大小優化: 線程池的最佳大小取決於可用的處理器數量和待處理任務的性質。對於CPU密集型任務,假設系統有N個邏輯處理核心,N 或 N+1 的最大線程池數量大小將實現最大效率。對於 I/O密集型任務,須要考慮請求的等待時間(W)和服務處理時間(S)的比例,線程池最大大小爲 N*(1+ W/S)會實現最高效率。
不要教條的使用上面的總結,須要根據本身的應用任務處理類型進行靈活的設置與調優,其中少不了測試實驗。
本文轉載註明出處(必須帶鏈接,不能只轉文字):字母哥博客 - zimug.com
以爲對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創做動力! 。另外,筆者最近一段時間輸出了以下的精品內容,期待您的關注。