1. 線程池
線程池是一種線程使用模式。線程過多會帶來額外的開銷,其中包括創(chuàng)建銷毀線程的開銷、調(diào)度線程的開銷等等,同時也降低了計算機的整體性能。線程池維護多個線程,等待監(jiān)督管理者分配可并發(fā)執(zhí)行的任務。這種做法,一方面避免了處理任務時創(chuàng)建銷毀線程開銷的代價,另一方面避免了線程數(shù)量膨脹導致的過分調(diào)度問題,保證了對內(nèi)核的充分利用。
使用線程池,有幾點好處:
- 降低資源消耗:通過池化技術(shù)重復利用已創(chuàng)建的線程,降低線程創(chuàng)建和銷毀造成的損耗。
- 提高響應速度:任務到達時,無需等待線程創(chuàng)建即可立即執(zhí)行。
- 提高線程的可管理性:線程是稀缺資源,如果無限制創(chuàng)建,不僅會消耗系統(tǒng)資源,還會因為線程的不合理分布導致資源調(diào)度失衡,降低系統(tǒng)的穩(wěn)定性。使用線程池可以進行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。
2. Java中線程池
Java開發(fā),我們使用JDK環(huán)境,開發(fā)框架基本都是Spring全家桶。線程池的基礎原理都在JDK中,JDK1.5新增線程池相關(guān)類,在核心jar包rt.jar中,java.util.concurrent下面。
2.1 JDK中線程池
看一下jdk中線程池繼承關(guān)系類圖:

可以看出,包含三個線程池:
ForkJoinPool,ThreadPoolExecutor,ScheduledThreadPoolExecutor。
-
ForkJoinPool是Java 1.7 引入的一種新的并發(fā)框架,核心思想是將大的任務拆分成多個小任務(fork),然后在將多個小任務處理匯總到一個結(jié)果上(join),充分利用多cpu,多核CPU的優(yōu)勢,引入了“work-stealing”機制,更有效的利用線程。 -
ThreadPoolExecutor是 java常用的線程池,提供基礎的線程池功能。初始化傳入不同類型的工作隊列和拒絕策略參數(shù),可以定制不同類型和功能的線程池,應用最為廣泛。 -
ScheduledThreadPoolExecutor從類圖上可以看出,它繼承了ThreadPoolExecutor,并實現(xiàn)了ScheduledExecutorService,是對ThreadPoolExecutor做的功能擴展,本質(zhì)上是一個使用線程池執(zhí)行定時任務的類,可以用來在給定延時后執(zhí)行異步任務或者周期性執(zhí)行任務,較任務調(diào)度的Timer來說,其功能更加強大。
2.2 JDK中提供的幾類線程池
JDK中,使用ThreadPoolExecutor給創(chuàng)建了幾個不同功能種類的線程池,可以調(diào)出來看看:
//創(chuàng)建一個核心線程數(shù)和最大線程數(shù)相同的線程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.execute(()->{System.out.println("this is fixed thread pool");});
//創(chuàng)建一個單線程的線程池
ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
singleExecutor.execute(() -> System.out.println("this is single thread pool"));
//創(chuàng)建一個緩存線程池
ExecutorService cacheExecutor = Executors.newCachedThreadPool();
cacheExecutor.execute(()-> System.out.println("this is cache thread pool"));
//創(chuàng)建一個延時執(zhí)行任務的線程池
ScheduledExecutorService scheduleExecutor = Executors.newScheduledThreadPool(4);
scheduleExecutor.schedule(()-> System.out.println("this is schedule thread pool"),2,TimeUnit.SECONDS);
看一下都有什么特點:
| 線程池名稱 | 使用阻塞隊列 | 特點 |
|---|---|---|
| newFixedThreadPool | LinkedBlockingQueue() | 1、核心線程數(shù)和最大線程數(shù)相同 2、由于keepAliveTime設置為0,當線程創(chuàng)建后會一直存在 3、由于用的是無界隊列所以可能會導致OOM |
| newSingleThreadExecutor | LinkedBlockingQueue() | 1、核心線程數(shù)和最大線程數(shù)都為1單線程 2、無界隊列可能導致OOM |
| newCachedThreadPool | SynchronousQueue() | 1、核心線程數(shù)為0,最大線程數(shù)為Integer.MAX_VALUE 2、當沒任務時線程存活時間為60秒 3、使用的是0大小的隊列,所以不存儲任務,只做任務轉(zhuǎn)發(fā) |
| newScheduledThreadPool | DelayedWorkQueue() | 1、執(zhí)行周期任務 2、無界隊列,可能會導致OOM |
從上面可以看出他們各有各的特點,但是阿里巴巴開發(fā)守則卻不推薦使用以上線程池,因為它們可能會對服務資源的浪費,所以推薦使用通過ThreadPoolExecutor自定線程池。
Spring中將Java中的線程池進行了封裝,而且提供了默認實現(xiàn),也能自定義線程池,我一般都用Spring中的線程池包。
3. Spring中的線程池
Spring中的線程池和JDK中的基本一樣,在org.springframework.scheduling.concurrent包下面。
和JDK中對應的,Spring的頂層接口是TaskExecutor,它實現(xiàn)了JDK中的Executor接口。
Spring中常用的線程池是ThreadPoolTaskExecutor,它是是借助于JDK并發(fā)包中的java.util.concurrent.ThreadPoolExecutor來實現(xiàn)的。
要想使用線程池,先了解一下線程池中的一些參數(shù):
3.1 線程池用到的一些參數(shù)
因為我們常用的是ThreadPoolExecutor線程池,所以去這個類中找。

也就這幾個常用參數(shù),在下面使用中再介紹意思。
3.2 使用自定義線程池
- 3.2.1 線程池配置類
package com.example.demo.threadpool;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import lombok.Data;
@Configuration
public class ThreadPoolConfig {
ThreadPoolProperties properties = new ThreadPoolProperties();
@Bean(name = "taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(properties.getCorePoolSize());
executor.setMaxPoolSize(properties.getMaxPoolSize());
executor.setQueueCapacity(properties.getQueueCapacity());
executor.setThreadNamePrefix(properties.getThreadNamePrefix());
// 設置線程保持活躍的時間(默認:60)
executor.setKeepAliveSeconds(properties.getKeepAliveTime());
// 當任務完成后,長時間無待處理任務時,銷毀線程池
executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
executor.setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds());
// 設置任務拒絕策略
/**
* 4種
* ThreadPoolExecutor類有幾個內(nèi)部實現(xiàn)類來處理這類情況:
- AbortPolicy 丟棄任務,拋RejectedExecutionException
- CallerRunsPolicy 由該線程調(diào)用線程運行。直接調(diào)用Runnable的run方法運行。
- DiscardPolicy 拋棄策略,直接丟棄這個新提交的任務
- DiscardOldestPolicy 拋棄舊任務策略,從隊列中踢出最先進入隊列(最后一個執(zhí)行)的任務
* 實現(xiàn)RejectedExecutionHandler接口,可自定義處理器
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Data
class ThreadPoolProperties {
/**
* 核心線程數(shù)(默認是1):若是IO密集型,cpu核心數(shù)*2,若是cpu密集型,cpu核心數(shù)
* 核心線程會一直存活,及時沒有任務需要執(zhí)行
* 設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關(guān)閉
* 注意:當線程數(shù)小于核心線程數(shù)時,即使有線程空閑,線程池也會優(yōu)先創(chuàng)建新線程處理
*/
private int corePoolSize = 8;
/**
* 最大線程數(shù),系統(tǒng)默認Integer.MAX_VALUE
*/
private int maxPoolSize = 20;
/**
* 允許線程空閑時間(單位:默認為秒,默認60S)
* 當線程空閑時間達到keepAliveTime時,線程會退出,直到線程數(shù)量=corePoolSize
* 如果allowCoreThreadTimeout=true,則會直到線程數(shù)量=0
*/
private int keepAliveTime;
/**
* 緩沖隊列大小,系統(tǒng)默認Integer.MAX_VALUE
* 注意:這個值肯定要改小,不然任務陡增時,都堆在隊列中(隊列值大),
* 核心線程數(shù)就那幾個,無法很快執(zhí)行隊列中的任務,
* 就會延長響應時間,阻塞任務
*/
private int queueCapacity = 6;
/**
* 線程池名前綴,用于監(jiān)控識別
*/
private String threadNamePrefix = "test";
/**
* 允許核心線程超時關(guān)閉(默認false,核心線程不關(guān)閉)
*/
private boolean allowCoreThreadTimeout = false;
/**
* 當任務完成后,長時間無待處理任務時,銷毀線程池,默認false,不銷毀
*/
private boolean waitForTasksToCompleteOnShutdown = false;
/**
* 強制停止應用時,線程池等待時間
* AwaitTerminationSeconds=0(默認為0)此時會立即停止線程池,
* 任務也會被強制停止,可能導致任務失敗或丟失
*/
private int awaitTerminationSeconds;
}
}
上面配置了線程池,并生成了線程池bean,交給了Spring容器管理,使用時注入即可使用。
- 3.2.2 使用線程池
SpringBoot使用線程池注意2點:- 添加
@EnableAsync注解開啟異步,即多線程 - 在要使用的方法上加上
@Async("taskExecutor")注解,括號內(nèi)是線程池名字
- 添加
package com.example.demo.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
@Service
@EnableAsync
public class ThreadPoolService {
@Async("taskExecutor")
public void F1() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
for(int i = 0 ; i < 10 ; i++){
System.out.println("A-"+i);
Thread.sleep(500);
}
}
@Async("taskExecutor")
public void F2() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
for(int i = 20 ; i < 30 ; i++){
System.out.println("B-"+i);
Thread.sleep(500);
}
}
}
- 3.2.3 @Async失效情況:
- 異步方法使用static修飾
- 2.異步類沒有使用@Component注解(或其他注解)導致spring無法掃描到異步類
- 異步方法不能與被調(diào)用的異步方法在同一個類中
- 類中需要使用@Autowired或@Resource等注解自動注入,不能自己手動new對象
- 如果使用SpringBoot框架必須在啟動類中增加@EnableAsync注解
3.3 SpringBoot中自動裝配的線程池
SpringBoot線程池自動裝配在spring-boot-autoconfigure這個jar中,在org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration類中。
自動裝配條件:

自動裝配的Bean名字給了2個,一個是
applicationTaskExecutor,一個是taskExecutor。如果自動裝配了,可以直接注入使用。
4. 線程池處理任務流程:
當往線程池中提交新任務時,線程池主要流程如下:
核心線程數(shù) -> 線程隊列 -> 最大線程數(shù) -> 拒絕策略
- 如果池中任務數(shù) < corePoolSize (核心線程數(shù)),創(chuàng)建新線程立即執(zhí)行任務
- 如果池中任務數(shù) > corePoolSize,新任務放到緩存隊列當中等待執(zhí)行
- 隊列滿,線程數(shù)量<maxPoolSize,新建線程立即執(zhí)行任務
- 隊列滿,線程數(shù)量>=maxPoolSize,使用拒絕策略拒絕
所以使用線程池,需要注意:
- 搞懂執(zhí)行流程,設置合適的
corePoolSize、maxPoolSize和隊列容量,避免延時任務,一般隊列容量不會像默認值那么大,否則可能等很長時間; - 注意使用時一些注解加上
- 要了解線程的一些基礎知識,比如生命周期等。
Java多線程--線程的狀態(tài) - 簡書 (jianshu.com)
Java多線程--線程常用方法 - 簡書 (jianshu.com)