線程池的簡介及底層原理


一、什么是線程池?

       線程池做的工作主要是控制運行的線程的數量,處理過程中將任務加入隊列,然后在線程創建后啟動這些任務,如果線程數超過了最大數量,超出的數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來執行。

線程池的主要特點為:線程復用、控制最大並發數、管理線程。

二、線程池的優勢

  1. 降低資源消耗。通過重復利用自己創建的線程,降低線程創建和銷毀造成的消耗。
  2. 提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
  3. 提高線程的可管理性。線程是稀缺資源,如果無限的創建,不僅會消耗資源,還會較低系統的穩定性,使用線程池可以進行統一分配,調優和監控。

三、架構實現

       Java 中的線程池是通過 Executor 框架實現的,該框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor 這幾個類。

ThreadPoolExecutor

四、線程池的幾種創建方式

4.0 ThreadPoolExecutor()

最原始的線程池創建,下面的前三種創建方式都是對 ThreadPoolExecutor 的封裝。

image

4.1 Executors.newFixedThreadPool(int)

image

主要特點如下:
1. 創建一個定長線程池,可控制線程的最大並發數,超出的線程會在隊列中等待.
2. newFixedThreadPool 創建的線程池 corePoolSize 和 MaxmumPoolSize 是相等的,它使用的的LinkedBlockingQueue。

4.2 Executors.newSingleThreadExecutor()

image

主要特點如下:
1. 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務都按照指定順序執行。
2. newSingleThreadExecutor 將 corePoolSize 和 MaxmumPoolSize 都設置為 1,它使用的是LinkedBlockingQueue。

4.3 Executors.newCachedThreadPool()

image

主要特點如下:
1. 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程;若無可回收,則創建新線程。
2. newCachedThreadPool 將 corePoolSize 設置為 0,MaxmumPoolSize 設置為 Integer.MAX_VALUE,它使用的是 SynchronousQueue,也就是說來了任務就創建線程運行,如果線程空閑超過60秒,就銷毀線程。

4.4 Executors.newWorkStealingPool()

image

Java 8 加入的創建方法,其內部會構建ForkJoinPool,利用Work-Stealing算法,並行地處理任務,不保證處理順序。

4.5 生產中的使用

       實際生產中不使用 Executors 創建線程池,需要使用 ThreadPoolExecutor 自定義創建。

阿里巴巴開發手冊中關於並發處理的部分內容:

image

自定義創建線程池:

  1 public static void main(String[] args) {
  2     ExecutorService threadPool = new ThreadPoolExecutor(
  3             2,
  4             5,
  5             1L,
  6             TimeUnit.SECONDS,
  7             new LinkedBlockingQueue<>(3),
  8             Executors.defaultThreadFactory(),
  9             new ThreadPoolExecutor.AbortPolicy());
 10 
 11     try {
 12         for (int i = 1; i <= 10; i++) {
 13             threadPool.execute(() -> {
 14                 System.out.println(Thread.currentThread().getName() + "\t 辦理業務");
 15             });
 16         }
 17     } finally {
 18         threadPool.shutdown();
 19     }
 20 }

五、線程池中的幾個重要參數

image

  • corePoolSize:線程池中的常駐核心線程數。
  • maximumPoolSize:線程池能夠容納的同時執行的最大線程數,此值大於等於 1。
  • keepAliveTime:多余的空閑線程存活時間,當空閑時間達到 keepAliveTime 值時,多余的線程會被銷毀,直到只剩下 corePoolSize 個線程為止。
  • unit:keepAliveTime 的單位。
  • workQueue:任務隊列,被提交但尚未被執行的任務。
  • threadFactory:表示生成線程池中工作線程的線程工廠,用戶創建新線程,一般用默認即可。
  • handler:拒絕策略,表示當線程隊列滿了並且工作線程大於等於線程池的最大線程數(maxnumPoolSize)時如何來拒絕。

六、線程池的底層工作原理

1. 在創建了線程池后,等待提交過來的任務請求。
2. 當調用 execute() 方法添加一個請求任務時,線程池會做如下判斷:
     2.1 如果正在運行的線程數量小於 corePoolSize,那么馬上創建線程運行這個任務;
     2.2 如果正在運行的線程數量大於或等於 corePoolSize,那么將這個任務放入隊列;
     2.3 如果這時候隊列滿了且正在運行的線程數量還小於 maximumPoolSize,那么還是要創建非核心線程立刻運行這個任務;
     2.4 如果隊列滿了且正在運行的線程數量大於或等於 maximumPoolSize,那么線程池會啟動飽和拒絕策略來執行。
3. 當一個線程完成任務時,它會從隊列中取下一個任務來執行。
4. 當一個線程無事可做,超過一定的時間(keepAliveTime) 時,線程池會判斷:
     如果當前運行的線程數大於 corePoolSize,那么這個線程就被停掉。
    所以線程池的所有任務完成后,它最終會收縮到 corePoolSize 的大小。

image

image

七、線程池的拒絕策略

       當線程池中工作線程數達到 maximumPoolSize,且工作隊列已滿,那么就會執行拒絕策略處理任務,jdk 內置有四種拒絕策略。

拒絕策略 拒絕行為

AbortPolicy

直接拋出 RejectedException 異常阻止系統正常運行

CallerRunsPolicy

"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是由提交任務者執行這個任務

DiscardOldestPolicy

拋棄隊列中等待最久的任務,然后把當前任務加入隊列中嘗試再次提交

DiscardPolicy

直接丟棄任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的拒絕策略

八、合理配置線程池參數

首先,通過 System.out.println(Runtime.getRuntime().availableProcessors()); 獲得當前計算機的 CPU 核數。

8.1 CPU 密集型

       CPU 密集的意思是該任務需要大量的運算,而沒有阻塞,CPU一直全速運行。
        CPU 密集任務只有在真正的多核 CPU 上才可能得到加速(通過多線程),而在單核CPU上,無論開幾個模擬的多線程,該任務都不可能得到加速,因為CPU總的運算能力就那些。
        CPU 密集型任務配置盡可能少的線程數量:
        一般公式:CPU核數+1個線程的線程池

8.2 IO密集型

  1. 由於 IO 密集型任務線程並不是一直在執行任務,則應配置盡可能多的線程,如 CPU 核數 * 2。
  2. IO密集型,即該任務需要大量的IO,即大量的阻塞。在單線程上運行 IO 密集型的任務會導致浪費大量的 CPU 運算能力在等待上。所以在 IO 密集型任務中使用多線程可以大大的加速程序運行,即使在單核CPU上,這種加速主要就是利用了被浪費掉的阻塞時間。
    IO 密集型時,大部分線程都阻塞,故需要多配置線程數:
    參考公式:CPU核數 / (1-阻塞系數)                     阻塞系數在0.8~0.9之間
    比如 8 核CPU:8 / (1 - 0.9) = 80 個線程數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM