在 Java 誕生之初是沒有線程池的概念的,而是先有線程。沒有線程池的時候,每發(fā)布一個任務(wù)就需要創(chuàng)建一個新的線程,這樣在任務(wù)少時是沒有問題的。隨著線程數(shù)的不斷增加,人們發(fā)現(xiàn)需要一個專門的類來管理它們,于是才誕生了線程池。
線程池種類


不使用線程池跑100000個線程
package com.example.threadpool;
/**
* @author liujy
* @description 不使用線程池跑100000個線程
* @since 2020-12-30 10:51
*/
public class WithoutThreadPool {
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
new Thread(new Task()).start();
}
}
// 一個簡單的task
static class Task implements Runnable {
@Override
public void run() {
System.out.println("my name is " + Thread.currentThread().getName());
}
}
}
運行結(jié)果如下:

創(chuàng)建了 10000 個子線程,而 Java 程序中的線程與操作系統(tǒng)中的線程是一一對應(yīng)的,此時假設(shè)線程中的任務(wù)需要一定的耗時才能夠完成,便會產(chǎn)生很大的系統(tǒng)開銷與資源浪費。
創(chuàng)建線程時會產(chǎn)生系統(tǒng)開銷,并且每個線程還會占用一定的內(nèi)存等資源,更重要的是我們創(chuàng)建如此多的線程也會給穩(wěn)定性帶來危害,因為每個系統(tǒng)中,可創(chuàng)建線程的數(shù)量是有一個上限的,不可能無限的創(chuàng)建。線程執(zhí)行完需要被回收,大量的線程又會給垃圾回收帶來壓力。但我們的任務(wù)確實非常多,如果都在主線程串行執(zhí)行,那效率也太低了,那應(yīng)該怎么辦呢?于是便誕生了線程池來平衡線程與系統(tǒng)資源之間的關(guān)系。
我們來總結(jié)下如果每個任務(wù)都創(chuàng)建一個線程會帶來哪些問題:
第一點,反復(fù)創(chuàng)建線程系統(tǒng)開銷比較大,每個線程創(chuàng)建和銷毀都需要時間,如果任務(wù)比較簡單,那么就有可能導(dǎo)致創(chuàng)建和銷毀線程消耗的資源比線程執(zhí)行任務(wù)本身消耗的資源還要大。
第二點,過多的線程會占用過多的內(nèi)存等資源,還會帶來過多的上下文切換,同時還會導(dǎo)致系統(tǒng)不穩(wěn)定。
線程池解決問題思路
針對上面的兩點問題,線程池有兩個解決思路。
首先,針對反復(fù)創(chuàng)建線程開銷大的問題,線程池用一些固定的線程一直保持工作狀態(tài)并反復(fù)執(zhí)行任務(wù)。
其次,針對過多線程占用太多內(nèi)存資源的問題,解決思路更直接,線程池會根據(jù)需要創(chuàng)建線程,控制線程的總數(shù)量,避免占用過多內(nèi)存資源。
如何使用線程池
線程池就好比一個池塘,池塘里的水是有限且可控的,比如我們選擇線程數(shù)固定數(shù)量的線程池,假設(shè)線程池有 5 個線程,但此時的任務(wù)大于 5 個,線程池會讓余下的任務(wù)進行排隊,而不是無限制的擴張線程數(shù)量,保障資源不會被過度消耗。
使用線程池跑100000個線程
package com.example.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author liujy
* @description 使用線程池跑100000個線程
* @since 2020-12-30 10:14
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10000; i++) {
executorService.execute(new Task());
}
}
// 一個簡單的task
static class Task implements Runnable {
@Override
public void run() {
System.out.println("my name is " + Thread.currentThread().getName());
}
}
}
運行結(jié)果如下:

如打印結(jié)果所示,打印的線程名始終在 Thread Name: pool-1-thread-1~5 之間變化,并沒有超過這個范圍,也就證明了線程池不會無限制地擴張線程的數(shù)量,始終是這5個線程在工作。

執(zhí)行流程如圖所示,首先創(chuàng)建了一個線程池,線程池中有 5 個線程,然后線程池將 10000 個任務(wù)分配給這 5 個線程,這 5 個線程反復(fù)領(lǐng)取任務(wù)并執(zhí)行,直到所有任務(wù)執(zhí)行完畢,這就是線程池的思想。
使用線程池的好處
使用線程池比手動創(chuàng)建線程主要有三點好處。
第一點,線程池可以解決線程生命周期的系統(tǒng)開銷問題,同時還可以加快響應(yīng)速度。因為線程池中的線程是可以復(fù)用的,我們只用少量的線程去執(zhí)行大量的任務(wù),這就大大減小了線程生命周期的開銷。而且線程通常不是等接到任務(wù)后再臨時創(chuàng)建,而是已經(jīng)創(chuàng)建好時刻準備執(zhí)行任務(wù),這樣就消除了線程創(chuàng)建所帶來的延遲,提升了響應(yīng)速度,增強了用戶體驗。
第二點,線程池可以統(tǒng)籌內(nèi)存和 CPU 的使用,避免資源使用不當。線程池會根據(jù)配置和任務(wù)數(shù)量靈活地控制線程數(shù)量,不夠的時候就創(chuàng)建,太多的時候就回收,避免線程過多導(dǎo)致內(nèi)存溢出,或線程太少導(dǎo)致 CPU 資源浪費,達到了一個完美的平衡。
第三點,線程池可以統(tǒng)一管理資源。比如線程池可以統(tǒng)一管理任務(wù)隊列和線程,可以統(tǒng)一開始或結(jié)束任務(wù),比單個線程逐一處理任務(wù)要更方便、更易于管理,同時也有利于數(shù)據(jù)統(tǒng)計,比如我們可以很方便地統(tǒng)計出已經(jīng)執(zhí)行過的任務(wù)的數(shù)量。