Java併發編程:線程池的使用

来源:https://www.cnblogs.com/Mr-kevin/archive/2018/01/21/8325612.html
-Advertisement-
Play Games

一. 準備工作 1. 本文參考 Java併發編程:線程池的使用 二. 相關代碼文件介紹 1. ThreadPoolExecutor.java 線程池中最核心的一個類,提供了四個構造函數用於創建線程池 public class ThreadPoolExecutor extends AbstractEx ...


一. 準備工作

  1. 本文參考 Java併發編程:線程池的使用 

 

二. 相關代碼文件介紹

  1. ThreadPoolExecutor.java  線程池中最核心的一個類,提供了四個構造函數用於創建線程池

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}
View Code

  下麵解釋下一下構造器中各個參數的含義:

corePoolSize:核心池的大小。在創建了線程池後,預設情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,
除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思,
即在沒有任務到來之前就創建corePoolSize個線程或者一個線程。預設情況下,在創建了線程池後,線程池中的線程數為0,當有任務
來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;

maximumPoolSize:線程池最大線程數,它表示線上程池中最多能創建多少個線程;

keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。預設情況下,只有當線程池中的線程數大於corePoolSize時,
keepAliveTime才會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閑的
時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)
方法,線上程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數為0;

unit:參數keepAliveTime的時間單位;

workQueue:一個阻塞隊列,用來存儲等待執行的任務;

threadFactory:線程工廠,主要用來創建線程;

handler:表示當拒絕處理任務時的策略;
View Code

  

  2. ThreadPoolExecutor類中有幾個重要的方法

execute():通過這個方法可以向線程池提交一個任務,交由線程池去執行。

submit():這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果

shutdown()和shutdownNow():是用來關閉線程池的
View Code

 

三. 深入剖析線程池實現原理

  1. 線程池狀態

RUNNING     當創建線程池後,初始時;

SHUTDOWN    當調用了shutdown()方法,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;

STOP        當調用了shutdownNow()方法,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務;

TERMINATED  當線程池處於SHUTDOWN或STOP狀態,並且所有工作線程已經銷毀,任務緩存隊列已經清空或執行結束後;
View Code

 

  2. 任務的執行

a. 如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;

b. 如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閑線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;

c. 如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;

d. 如果線程池中的線程數量大於 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許為核心池中的線程設置存活時間,那麼核心池中的線程空閑時間超過keepAliveTime,線程也會被終止。
View Code

 

  3. 線程池中的線程初始化 

    預設情況下,創建線程池之後,線程池中是沒有線程的,需要提交任務之後才會創建線程。

    在實際中如果需要線程池創建之後立即創建線程,可以通過以下兩個方法辦到:

prestartCoreThread():初始化一個核心線程;
prestartAllCoreThreads():初始化所有核心線程
View Code

  

  4. 任務緩存隊列及排隊策略

    workQueue的類型為BlockingQueue<Runnable>,通常可以取下麵三種類型:

1)ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小;

2)LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則預設為Integer.MAX_VALUE;

3)synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。
View Code

 

  5. 任務拒絕策略

    當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重覆此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
View Code

 

四. 使用示例

public class Test {
     public static void main(String[] args) {   
         ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                 new ArrayBlockingQueue<Runnable>(5));
          
         for(int i=0;i<15;i++){
             MyTask myTask = new MyTask(i);
             executor.execute(myTask);
             System.out.println("線程池中線程數目:"+executor.getPoolSize()+",隊列中等待執行的任務數目:"+
             executor.getQueue().size()+",已執行玩別的任務數目:"+executor.getCompletedTaskCount());
         }
         executor.shutdown();
     }
}
 
 
class MyTask implements Runnable {
    private int taskNum;
     
    public MyTask(int num) {
        this.taskNum = num;
    }
     
    @Override
    public void run() {
        System.out.println("正在執行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"執行完畢");
    }
}
View Code

  執行結果:

正在執行task 0
線程池中線程數目:1,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0
線程池中線程數目:2,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0
正在執行task 1
線程池中線程數目:3,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0
正在執行task 2
線程池中線程數目:4,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0
正在執行task 3
線程池中線程數目:5,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0
正在執行task 4
線程池中線程數目:5,隊列中等待執行的任務數目:1,已執行玩別的任務數目:0
線程池中線程數目:5,隊列中等待執行的任務數目:2,已執行玩別的任務數目:0
線程池中線程數目:5,隊列中等待執行的任務數目:3,已執行玩別的任務數目:0
線程池中線程數目:5,隊列中等待執行的任務數目:4,已執行玩別的任務數目:0
線程池中線程數目:5,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0
線程池中線程數目:6,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0
正在執行task 10
線程池中線程數目:7,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0
正在執行task 11
線程池中線程數目:8,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0
正在執行task 12
線程池中線程數目:9,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0
正在執行task 13
線程池中線程數目:10,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0
正在執行task 14
task 3執行完畢
task 0執行完畢
task 2執行完畢
task 1執行完畢
正在執行task 8
正在執行task 7
正在執行task 6
正在執行task 5
task 4執行完畢
task 10執行完畢
task 11執行完畢
task 13執行完畢
task 12執行完畢
正在執行task 9
task 14執行完畢
task 8執行完畢
task 5執行完畢
task 7執行完畢
task 6執行完畢
task 9執行完畢
View Code

    從執行結果可以看出,當線程池中線程的數目大於5時,便將任務放入任務緩存隊列裡面,當任務緩存隊列滿了之後,便創建

  新的線程。如果上面程式中,將for迴圈中改成執行20個任務,就會拋出任務拒絕異常了。

 

五. Executors類

  在java中,並不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個靜態方法來創建線程池:

Executors.newCachedThreadPool();        //創建一個緩衝池,緩衝池容量大小為Integer.MAX_VALUE
Executors.newSingleThreadExecutor();   //創建容量為1的緩衝池
Executors.newFixedThreadPool(int);    //創建固定容量大小的緩衝池
View Code

  下麵是這三個靜態方法的具體實現;

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>());
}
View Code

  下麵是對這三個方法的解釋:

newFixedThreadPool創建的線程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

newSingleThreadExecutor將corePoolSize和maximumPoolSize都設置為1,也使用的LinkedBlockingQueue;

newCachedThreadPool將corePoolSize設置為0,將maximumPoolSize設置為Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就創建線程運行,當線程空閑超過60秒,就銷毀線程。
View Code

 

六. 如何合理配置線程池的大小,僅供參考

  一般需要根據任務的類型來配置線程池大小:

  如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設為 NCPU+1
  如果是IO密集型任務,參考值可以設置為2*NCPU

  當然,這隻是一個參考值,具體的設置還需要根據實際情況進行調整。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 呵呵!作為一名教python的老師,我發現學生們基本上一開始很難搞定python的裝飾器,也許因為裝飾器確實很難懂。搞定裝飾器需要你瞭解一些函數式編程的概念,當然還有理解在python中定義和調用函數相關語法的一些特點。 我沒法讓裝飾器變得簡單,但是通過一步步的剖析,我也許能夠讓你在理解裝飾器的時候 ...
  • 轉載自:http://in355hz.iteye.com/blog/1860787 最近業務中需要用 Python 寫一些腳本。儘管腳本的交互只是命令行 + 日誌輸出,但是為了讓界面友好些,我還是決定用中文輸出日誌信息。 很快,我就遇到了異常: Python代碼 UnicodeEncodeError ...
  • 給定一個字元串,求它最長的迴文子串長度,例如輸入字元串'35534321',它的最長迴文子串是'3553',所以返回4。 最容易想到的辦法是枚舉出所有的子串,然後一一判斷是否為迴文串,返回最長的迴文子串長度。不用我說,枚舉實現的耗時是我們無法忍受的。那麼有沒有高效查找迴文子串的方法呢?答案當然是肯定 ...
  • Servlet簡單實現請求分發(類thinkphp5) 1.寫請求分發的原由 今天晚上筆者在使用java的servlet寫博客網站的時候,想實現MVC開發模式,然後就發現,一個請求的動作就要寫servlet實現類,這也太麻煩了吧,於是就在想,可不可以一個contorller控制器中實現多個請求,像t ...
  • 函數是組織好的,可重覆使用的,用來實現單一,或相關聯功能的代碼段。函數能提高應用的模塊性,和代碼的重覆利用率。你已經知道Python提供了許多內建函數,比如print()。但你也可以自己創建函數,這被叫做用戶自定義函數。 1、語法 Python 定義函數使用 def 關鍵字,一般格式如下: 預設情況 ...
  • 上一篇文章是我們自己模擬的DBUtils工具類,其實有開發好的工具類 這裡使用commons-dbutils-1.6.jar 事務的簡單介紹: 在資料庫中應用事務處理案例:轉賬案例 張三和李四都有有自己的存款 主鍵 帳戶名 餘額 1 張三 1000 2 李四 10 要從張三的賬戶餘額中轉賬800到李 ...
  • 1.編程方式分:面向對象、面向過程、函數式編程 2.區分面向對象 》類 》class面向過程 》過程 》def函數式編程 》函數 》def 3.編程語言中函數的定義: 函數是邏輯結構化和過程化的一種編程方法 4.過程是沒有返回值的函數 5.使用函數的優點: 1)代碼可重覆使用2)代碼可保持一致性3) ...
  • 分支運算;邏輯運算;隨機數import;賦值語句;break和continue;轉義字元;print加強版的使用 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...