在 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ù)進(jìn)行排隊拒课,而不是無限制的擴(kuò)張線程數(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 之間變化劝堪,并沒有超過這個范圍,也就證明了線程池不會無限制地擴(kuò)張線程的數(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ǔn)備執(zhí)行任務(wù),這樣就消除了線程創(chuàng)建所帶來的延遲脆霎,提升了響應(yīng)速度狈惫,增強(qiáng)了用戶體驗睛蛛。
第二點胧谈,線程池可以統(tǒng)籌內(nèi)存和 CPU 的使用,避免資源使用不當(dāng)菱肖。線程池會根據(jù)配置和任務(wù)數(shù)量靈活地控制線程數(shù)量,不夠的時候就創(chuàng)建场仲,太多的時候就回收键袱,避免線程過多導(dǎo)致內(nèi)存溢出,或線程太少導(dǎo)致 CPU 資源浪費蹄咖,達(dá)到了一個完美的平衡。
第三點,線程池可以統(tǒng)一管理資源舵匾。比如線程池可以統(tǒng)一管理任務(wù)隊列和線程谁不,可以統(tǒng)一開始或結(jié)束任務(wù),比單個線程逐一處理任務(wù)要更方便刹帕、更易于管理,同時也有利于數(shù)據(jù)統(tǒng)計偷溺,比如我們可以很方便地統(tǒng)計出已經(jīng)執(zhí)行過的任務(wù)的數(shù)量。