一、什么是线程池
线程池(Thread Pool)是一种多线程并发执行的设计模式,它通过维护一个线程集合来执行多个任务,避免频繁地创建和销毁线程,提高系统性能和响应速度。
就好比如你经营了一家餐饮店,你名下有专职外卖员,为了防止没生意和生意爆满,你就雇了五个骑手,这五个骑手就好比线程,然后每个骑手都能安排接送一些订单,相当于任务,然后还没执行的任务就记录到一个本子上,等骑手执行完手上的任务再回来继续执行本子上的任务。
二、线程池的作用
- 线程复用:线程池中有多个线程可以重复使用,而不需要每次都创建新线程,减少了线程创建和销毁的开销。
- 任务队列:线程池内部有一个任务队列,当任务提交时,它们会先进入队列,等待线程池中的线程来执行。
- 线程管理:线程池负责管理线程的生命周期,控制最大线程数、最小线程数等。
- 任务调度:线程池按照设定的规则分配任务,保证任务的并发执行。
三、线程池的参数介绍
1. corePoolSize(核心线程数)
- 定义:线程池中保持活动的最小线程数,即使这些线程处于空闲状态,它们也不会被销毁。
- 作用:它是线程池中最少的线程数。如果线程池中的线程数量小于这个值,线程池会创建新的线程来处理任务。
- 适用场景:当系统负载较重时,确保线程池有足够的线程可用来处理任务。
2. maximumPoolSize(最大线程数)
- 定义:线程池能够容纳的最大线程数。当线程池中的线程数达到该值时,如果有新的任务提交,将根据
workQueue
的状态进行处理。 - 作用:限制线程池可以创建的最大线程数,避免线程过多导致资源浪费或系统崩溃。
- 适用场景:当任务量突增时,允许线程池创建更多的线程来处理任务,防止任务积压。
3. keepAliveTime(线程空闲存活时间)
- 定义:当线程池中线程数量超过
corePoolSize
时,空闲线程存活的最大时间。超时后,空闲线程会被销毁,直到线程池中的线程数达到corePoolSize
。 - 作用:控制线程池中空闲线程的生命周期,避免不必要的资源浪费。如果线程池中有多余线程,且线程空闲时间超过
keepAliveTime
,这些线程会被回收。 - 适用场景:如果系统负载较低,线程池中的线程不需要一直存在,可以通过设置合理的
keepAliveTime
来减少资源占用。
4. workQueue(任务队列)
-
定义:当线程池中的线程数量达到
corePoolSize
时,新的任务会被存入任务队列等待执行。常见的任务队列有:- SynchronousQueue:每个任务必须由一个线程来处理,否则会拒绝该任务(不允许任务排队)。
- LinkedBlockingQueue:队列长度无限,任务会排队等待线程来执行,适用于任务数很多但线程数有限的情况。
- ArrayBlockingQueue:队列长度有限,队列容量大小需要在创建时指定。
- PriorityBlockingQueue:优先级队列,任务按优先级顺序处理。
-
作用:任务队列决定了线程池如何管理和调度任务。如果所有线程都忙碌且任务队列已满,则新提交的任务会根据
RejectedExecutionHandler
策略处理。
5. ThreadFactory(线程工厂)
- 定义:一个用于创建新线程的工厂接口。线程池使用这个工厂来创建新线程,而不是直接使用
new Thread()
。 - 作用:通过实现
ThreadFactory
接口,用户可以定制线程的创建方式,比如设置线程名称、优先级、是否为守护线程等。 - 适用场景:需要定制线程属性的场景,例如给线程命名、设置线程的特定属性(如守护线程、优先级等)。
6. RejectedExecutionHandler(任务拒绝处理器)
-
定义:当线程池中的线程数达到
maximumPoolSize
,并且任务队列已满时,新的任务提交会被拒绝执行。此时,RejectedExecutionHandler
决定如何处理这些任务。 -
常见策略:
- AbortPolicy:直接抛出
RejectedExecutionException
,默认策略。 - CallerRunsPolicy:由调用者线程来执行任务,而不是由线程池中的线程来执行。
- DiscardPolicy:丢弃当前任务,不抛出异常。
- DiscardOldestPolicy:丢弃任务队列中最旧的任务,然后尝试提交当前任务。
- AbortPolicy:直接抛出
-
作用:当线程池达到最大容量并且任务队列已满时,确定如何应对新的任务请求。
7. AllowCoreThreadTimeOut(是否允许核心线程超时)
- 定义:这是一个布尔值,表示是否允许核心线程在空闲时超时被销毁。如果设置为
true
,即使是核心线程也会在超时后被销毁;如果为false
,核心线程永远不会被销毁。 - 作用:允许核心线程在不使用时被销毁,节省系统资源。
四、线程池的工作流程
1.任务提交
当你通过submit()或者execute()方法向线程池提交任务时,线程池首先会判断当前线程池的状态。
2.任务队列的选择
线程池会根据线程池当前的状态(线程数、队列大小等)来决定任务去向:
-
如果当前线程池中的线程数小于
corePoolSize(核心线程数)
:线程池会创建一个新的线程来执行任务,即使其他线程是空闲的。这时线程池没有达到核心线程数,所以新任务会直接交给新线程处理。 -
如果当前线程池中的线程数已经等于或大于
corePoolSize
:任务会被放入任务队列(workQueue
)中等待。此时,不会创建新的线程,任务将等待已有线程去执行。
3. 执行任务
-
如果任务在队列中等待:线程池中的线程会从队列中获取任务并执行。
-
如果任务队列已满且线程数还未达到
maximumPoolSize
:线程池会根据maximumPoolSize
参数创建更多的线程来执行任务。 -
如果任务队列已满且线程数已达到
maximumPoolSize
:线程池无法再创建新的线程,新的任务会根据线程池的拒绝策略(RejectedExecutionHandler
)来进行处理,常见的拒绝策略有:- AbortPolicy:直接抛出异常(默认策略)。
- CallerRunsPolicy:由提交任务的线程来执行该任务。
- DiscardPolicy:丢弃当前任务。
- DiscardOldestPolicy:丢弃任务队列中最旧的任务,并尝试重新提交任务。
4. 线程的回收
-
空闲线程回收:当线程池中的线程数超过
corePoolSize
且线程处于空闲状态,线程池会在等待一定时间后回收这些空闲线程。这个时间由keepAliveTime
参数控制。 -
核心线程回收:默认情况下,核心线程会一直保留,即使它们处于空闲状态。如果设置了
allowCoreThreadTimeOut
为true
,那么核心线程也会在空闲时间超时后被回收。
5. 线程池关闭
当线程池执行完所有任务,调用 shutdown()
或 shutdownNow()
方法后,线程池会进行关闭操作:
-
shutdown()
:线程池不会接受新的任务,但会继续执行已提交的任务,直到所有任务执行完毕,之后线程池才会关闭。 -
shutdownNow()
:会尽量停止正在执行的任务,并返回尚未执行的任务列表。这个方法会强制中止线程池的运行,但不能保证所有任务都能被成功中止。
五、使用Executors 创建常见的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ThreadPoolExample {public static void main(String[] args){//固定线程池ExecutorService fixedThreadPool= Executors.newFixedThreadPool(3);for(int i=0;i<5;i++){fixedThreadPool.submit(()->System.out.println(Thread.currentThread().getName()));}//缓存线程池ExecutorService cachedThreadPool = Executors.newCachedThreadPool();for (int i = 0; i < 5; i++) {cachedThreadPool.submit(() -> System.out.println(Thread.currentThread().getName() ));}//单线程池ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();for (int i = 0; i < 5; i++) {singleThreadExecutor.submit(() -> System.out.println(Thread.currentThread().getName()));}//定时线程池ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName()), 0, 1, TimeUnit.SECONDS);}}
Executors 本质上是 ThreadPoolExecutor 类的封装. ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定。