詳解線程池的做用及Java中如何使用線程池


服務端應用程序(如數據庫和 Web 服務器)須要處理來自客戶端的高併發、耗時較短的請求任務,因此頻繁的建立處理這些請求的所須要的線程就是一個很是消耗資源的操做。常規的方法是針對一個新的請求建立一個新線程,雖然這種方法彷佛易於實現,但它有重大缺點。爲每一個請求建立新線程將花費更多的時間,在建立和銷燬線程時花費更多的系統資源。所以同時建立太多線程的 JVM 可能會致使系統內存不足,這就須要限制要建立的線程數,也就是須要使用到線程池。vue

1、什麼是 Java 中的線程池?

線程池技術就是線程的重用技術,使用以前建立好的線程來執行當前任務,並提供了針對線程週期開銷和資源衝突問題的解決方案。 因爲請求到達時線程已經存在,所以消除了線程建立過程致使的延遲,使應用程序獲得更快的響應。spring

  • Java提供了以Executor接口及其子接口ExecutorServiceThreadPoolExecutor爲中心的執行器框架。經過使用Executor,完成線程任務只需實現 Runnable接口並將其交給執行器執行便可。
  • 爲您封裝好線程池,將您的編程任務側重於具體任務的實現,而不是線程的實現機制。
  • 若要使用線程池,咱們首先建立一個 ExecutorService對象,而後向其傳遞一組任務。ThreadPoolExcutor 類則能夠設置線程池初始化和最大的線程容量。

上圖表示線程池初始化具備3 個線程,任務隊列中有5 個待運行的任務對象。數據庫

執行器線程池方法編程

方法 描述
newFixedThreadPool(int) 建立具備固定的線程數的線程池,int參數表示線程池內線程的數量
newCachedThreadPool() 建立一個可緩存線程池,該線程池可靈活回收空閒線程。若無空閒線程,則新建線程處理任務。
newSingleThreadExecutor() 建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務
newScheduledThreadPool 建立一個定長線程池,支持定時及週期性任務執行

在固定線程池的狀況下,若是執行器當前運行的全部線程,則掛起的任務將放在隊列中,並在線程變爲空閒時執行。後端

2、線程池示例

在下面的內容中,咱們將介紹線程池的executor執行器。緩存

建立線程池處理任務要遵循的步驟springboot

  1. 建立一個任務對象(實現Runnable接口),用於執行具體的任務邏輯
  2. 使用Executors建立線程池ExecutorService
  3. 將待執行的任務對象交給ExecutorService進行任務處理
  4. 停掉 Executor 線程池
//第一步: 建立一個任務對象(實現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個請求分配給該線程,並將繼續對全部剩餘的請求執行相同的操做。在系統資源比較緊張的狀況下,線程池是保證程序穩定運行的一個有效的解決方案。

3、使用線程池的注意事項與調優

  1. 死鎖: 雖然死鎖可能發生在任何多線程程序中,但線程池引入了另外一個死鎖案例,其中全部執行線程都在等待隊列中某個阻塞線程的執行結果,致使線程沒法繼續執行。
  2. 線程泄漏 : 若是線程池中線程在任務完成時未正確返回,將發生線程泄漏問題。例如,某個線程引起異常而且池類沒有捕獲此異常,則線程將異常退出,從而線程池的大小將減少一個。若是這種狀況重複屢次,則線程池最終將變爲空,沒有線程可用於執行其餘任務。
  3. 線程頻繁輪換: 若是線程池大小很是大,則線程之間進行上下文切換會浪費不少時間。因此在系統資源容許的狀況下,也不是線程池越大越好。

線程池大小優化: 線程池的最佳大小取決於可用的處理器數量和待處理任務的性質。對於CPU密集型任務,假設系統有N個邏輯處理核心,N 或 N+1 的最大線程池數量大小將實現最大效率。對於 I/O密集型任務,須要考慮請求的等待時間(W)和服務處理時間(S)的比例,線程池最大大小爲 N*(1+ W/S)會實現最高效率。

不要教條的使用上面的總結,須要根據本身的應用任務處理類型進行靈活的設置與調優,其中少不了測試實驗。

歡迎關注個人博客,裏面有不少精品合集

本文轉載註明出處(必須帶鏈接,不能只轉文字):字母哥博客 - zimug.com

以爲對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創做動力! 。另外,筆者最近一段時間輸出了以下的精品內容,期待您的關注。

相關文章
相關標籤/搜索