目录
2. 关闭线程池(shutdown、shutdownNow)
一、什么是线程池
线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待空闲状态。如果有新的线程任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,线程池会创建一个新线程进行处理或者放入队列(工作队列)中等待。
二、线程池常用类和接口
在 Java 标准库提供了如下几个类或接口,来操作并使用线程池:
(1)ExecutorService 接口:进行线程池的操作访问
(2)Executors 类:创建线程池的工具类
// 固定数目的线程池
ExecutorService exector = Executors.newFixedThreadPool(3)
// 动态数目的线程池(线程被缓存,提供重复使用效率)
ExecutorService exector = Executors.newCachedThreadPool()
// 仅包含1个线程的线程池(将大量的线程任务保存至工作队列,然后按照提交顺序,用一条线程依次处理)
ExecutorService exector = Executors.newSingleThreadExecutor()
// 可以按照时间进行调度执行任务的线程池
ExecutorService exector = Executors.newScheduledThreadPool(5)
(3)ThreadPoolExecutor 及其子类:封装线程池的核心参数和运行机制
// 手动创建线程池
ExecutorService executorService = new ThreadPoolExecutor(
5, 20, 60, TimeUnit.SECONDS, // 核心线程数为5,最大线程数为20,非核心线程存活时间为60s
new LinkedBlockingQueue<Runnable>() // 工作队列,无界(容量)
// ArrayBlockingQueue<Runnable>(30) 有界,容量为30
);
三、线程池常见方法
- 执行无返回值的线程任务:void execute(Runnable command)
- 提交有返回值的线程任务:Future<T>submit(Callable<T>task)
- 关闭线程池:void shutdown()或 shutdownNow()
- 等待线程池关闭:boolean awaitTermination(long timeout,TimeUnit unit)
1. 执行线程任务(execute、submit)
execute()只能提交 Runnable类型的任务,没有返回值,而submit()既能提交 Runnable 类型任务也能提交Callable 类型任务,可以返回 Future 类型结果,用于获取线程任务执行结果。
execute()方法提交的任务异常是直接抛出的,而submit()方法是捕获异常,当调用 Future 的 get()方法获取返回值时,才会抛出异常。
2. 关闭线程池(shutdown、shutdownNow)
线程池在程序结束的时候要关闭。使用shutdown()方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭。shutdownNow()会立刻停止正在执行的任务;
当使用 awaitTermination()方法时,主线程会处于一种等待的状态,按照指定的 timeout 检查线程池。
第一个参数指定的是时间,第二个参数指定的是时间单位(当前是秒)。返回值类型为 boolean 型。
如果等待的时间超过指定的时间,但是线程池中的线程运行完毕,awaitTermination()返回 true
如果等待的时间超过指定的时间,但是线程池中的线程未运行完毕,waitTermination()返false
如果等待时间没有超过指定时间,则继续等待。
四、线程池的执行流程
1. 提交一个新线程任务,线程池会在线程池中分配一个空闲线程,用于执行线程任务;
2. 如果线程池中不存在空闲线程,则线程池会判断当前“存活的线程数”是否小于核心线程数corePoolsize。
2.1. 如果小于核心线程数corePoolsize,线程池会创建一个新线程(核心线程)去处理新线程任务;
2.2. 如果大于核心线程数 corePoolsize,线程池会检查工作队列。
2.2.1. 如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行;
2.2.2. 如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize。
2.2.2.1. 如果当前“存活线程数”没有达到最大线程数 maximumPoolsize,则创建一个新线程(非核心线程)执行新线程任务;
2.2.2.2. 如果当前“存活线程数”已经达到最大线程数 maximumPoolSize直接采用拒绝策略处理新线程任务。
综上所述,执行顺序为:核心线程、工作队列、非核心线程、拒绝策略。
五、线程池的配置参数
1. corePoolsize 线程池核心线程数:也可以理解为线程池维护的最小线程数量,核心线程创建后不会被回收。大于核心线程数的线程,在空闲时间超过 keepAliveTime 后会被回收;
在创建了线程池后,默认情况下,线程池中并没有任何线程,当调用 execute()方法添加一个任务时,如果正在运行的线程数量小于 corePoolsize,则马上创建新线程并运行这个任务。
2. maximumPoolsize 线程池最大线程数:线程池允许创建的最大线程数量;(包含核心线程池数量)。
3. keepAliveTime 非核心线程线程存活时间:当一个可被回收的线程的空闲时间大于 keepAliveTime ,就会被回收。
当线程池中的线程数大于 corePoolsize 时,如果一个线程空闲的时间达到 keepAliveTime ,则会被回收,直到线程池中的线程数不超过 corePoolsize;
如果设置 allowCoreThreadTime0ut =true,在线程池中的线程数不大于 corePoolsize 时,keepAliveTime 参数也会起作用,直到线程池中的线程数为0。
4. TimeUnit 时间单位:参数 keepAliveTime 的时间单位。
5. BlockingQueue 阻塞工作队列:用来存储等待执行的任务。
6. ThreadFactory 线程工厂:用于创建线程,以及自定义线程名称,需要实现ThreadFactory 接口。
7. RejectedExecutionHandler 拒绝策略:当线程池线程内的线程耗尽,并日工作队列达到已满时,新提交的任务将使用拒绝策略进行处理;
ThreadPoolExecutor.AbortPolicy :默认策略,丢弃任务并抛出 RejectedExecutionException 异常;
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常;
ThreadPoolExecutor.Discard0ldestPolicy:丢弃工作队列中的队头任务(即最旧的任务,也就是最早进入队列的任务)后,继续将当前任务提交给线程池;
ThreadPoolExecutor.callerRunsPolicy:由原调用线程处理该任务(谁调用,谁处理);
自定义策略:实现RejectedExecutionHandler接口。
五、线程池的分类
Java 标准库提供的几种常用线程池,创建这些线程池的方法都被封装
到 Executors 具类中。
- FixedThreadPool:线程数固定的线程池,使用 Executors.newFixedThreadPool()创建;
- CachedThreadPool:线程数根据任务动态调整的线程池,使Executors.newCachedThreadPool()创建;
- SingleThreadExecutor:仅提供一个单线程的线程池,使用Executors.newSingleThreadExecutor()创建;
- ScheduledThreadPool:能实现定时、周期性任务的线程池,使用Executors.newScheduledThreadPool()创建;
六、线程池的状态
线程池的状态分为:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。
RUNNING:运行状态,线程池被一旦被创建,就处于 RUNNING 状态,并且线程池中的任务数为0。该状态的线程池会接收新任务,并处理工作队列中的任务。
调用线程池的 shutdown()方法,可以切换到SHUTDOWN关闭状态;
调用线程池的 shutdownNow()方法,可以切换到 STOP停止状态;
SHUTDOWN:关闭状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;
当工作队列为空时,并且线程池中执行的任务也为空时,线程池进入 TIDYING 状态;
STOP:停止状态,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行 的任务;
线程池中执行的任务为空,进入 TIDYING 状态:
TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;
terminated()执行完毕,进入TERMINATED 状态;
TERMINATED:终止状态,该状态表示线程池彻底关闭。
七、线程池分类总结
1. FixedThreadPool
线程数固定的线程池
线程池参数:
a. 核心线程数和最大线程数一致
b. 非核心线程线程空闲存活时间,即 keepAliveTime为0
c. 阻塞队列为无界队列 LinkedBlockingQueue
工作机制:
a. 提交线程任务
b. 如果线程数少于核心线程,创建核心线程执行任务
c. 如果线程数等于核心线程,把任务添加到LinkedBlockingQueue 阻塞队列
d. 如果线程执行完任务,去阻塞队列取任务,继续执行
使用场景:适用于处理CPU 密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
2. CachedThreadPool
可缓存线程池,线程数根据任务动态调整的线程池
线程池参数:
a. 核心线程数为 0
b. 最大线程数为 Integer.MAX_VALUE
c. 工作队列是 SynchronousQueue 同步队列
d. 非核心线程空闲存活时间为 60 秒
工作机制:
a. 提交线程任务
b. 因为核心线程数为 0,所以任务直接加到SynchronousQueue 工作队列
c. 判断是否有空闲线程,如果有,就去取出任务执行
d. 如果没有空闲线程,就新建一个线程执行
e. 执行完任务的线程,还可以存活60 秒,如果在这期间,接到任务,可以继续存活下去;否则,被销毁。
使用场景: 用于并发执行大量短期的小任务
3. SingleThreadExecutor
单线程化的线程池
线程池参数:
a. 核心线程数为 1
b. 最大线程数也为 1
c. 阻塞队列是 LinkedBlockingQueue
d. 非核心线程空闲存活时间为 0秒
使用场景: 适用于串行执行任务的场景,将任务按顺序执行。
4.ScheduledThreadPool
能实现定时、周期性任务的线程池
线程池参数:
a. 最大线程数为 Integer.MAXVALUE
b. 阻塞队列是 DelayedWorkQueue
c. keepAliveTime为0
使用场景:周期性执行任务,并且需要限制线程数量的需求场景。