1 创建线程池
使用ThreadPoolExecutor构造器来创建线程池
现在来看看workQueue(任务队列)如何设置。
workQueue是BlockingQueue<Runnable>
的子类,有以下可选项:
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。
- SynchronousQueue:一个不存储元素的阻塞队列。每个��入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
1.2 使用Executors工厂类
Executors
类中提供了很多工厂方法,可以创建几种类型的ThreadPoolExecutor。
FixedThreadPool
固定线程数的线程池,corePoolSize和maximumPoolSize设置为相同的值,即全是核心线程,无法创建应急线程,线程池中线程也不会因空闲而终止。适用于任务量已知,相对耗时的任务。
使用无界队列LinkedBlockingQueue作为工作队列(容量为Integer.MAX_VALUE),有如下影响:
- 当线程池中的线程数达到corePoolSize后,新任务都将放入无界队列中等待;
- maximumPoolSize、keepAliveTime将是无效参数。
- 运行中的FixedThreadPool不会拒绝任何任务,因此较难感知到任务是否积压。
SingleThreadExecutor
整理了这份Java面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处】即可免费获取
corePoolSize和maximumPoolSize被设置为1,其他参数与FixedThreadPool相同。
CachedThreadPool
这是一个会根据需要动态创建新线程的线程池。
- corePoolSize设置为0,maximumPoolSize被设置为Integer.MAX_VALUE,相当于可以不受限制的创建应急线程。
- keepAliveTime设置为60L,空闲线程超过60秒后将会被终止。
- 使用没有容量的SynchronousQueue作为线程池的工作队列。如果提交任务的速度高于已有线程处理速度时,CachedThreadPool会不断创建新线程。极端情况下,会因为创建过多线程而耗尽CPU和内存资源。
CachedThreadPool适合任务数比较密集,但每个任务执行时间较短的情况。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。功能与Timer类似,但它的功能更强大、更灵活。
DelayQueue是一个无界队列,因此maximumPoolSize失去意义。
当调用scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask。 ScheduledFutureTask主要包含3个成员变量:
- long time,表示这个任务将要被执行的具体时间。
- long sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。
- long period,表示任务执行的间隔周期。
DelayQueue封装了一个PriorityQueue,会对队列中的ScheduledFutureTask
进行排序,time小的排在前面,time相同时sequenceNumber小的排在前面(先提交的任务将被先执行)。
当某个线程执行一个到期任务时,会将任务的time更新为下次执行时间,并重新放回队列中排序。
2 关闭线程池
ThreadPoolExecutor
有两个方法可以关闭线程池:shutdown和shutdownNow。原理都是遍历所有工作线程,逐个调用Thread.interrupt()来中断线程。
共同点:当调用以上任意一个方法后,ThreadPoolExecutor.isShutdown会返回true,且线程池将拒绝提交新的任务。
它俩的区别在于:
- shutdown设置线程池状态为SHUTDOWN,只会interruptIdleWorkers,即立即中断等待任务的线程,而对正在执行任务的线程不会中断。即执行中的任务会继续执行,任务队列中剩余任务也会被执行。
- shutdownNow设置线程池状态为STOP,会调用interruptIfStarted,即中断所有已经启动的线程,即使线程正在某个执行,并返回任务队列中的剩余任务。
该使用哪一个方法呢?这取决于任务特性,通常使用shutdown;如果任务不一定要执行完,则可以调用shutdownNow。
注意:如果任务无法响应,线程可能不会如期终止。如下面的代码:由于runnable1无法响应interrupt,导致线程池始终有活跃线程。
java
代码解读
复制代码
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.ShutdownThread") public class ShutdownThread { public static final ThreadPoolExecutor EXECUTOR_SERVICE = (ThreadPoolExecutor) Executors.newFixedThreadPool(3); public static void main(String[] args) throws InterruptedException { // 启动3个基本线程 EXECUTOR_SERVICE.prestartAllCoreThreads(); Runnable runnable1 = () -> { //用死循环模拟没有超时时间的 RPC调用 while (true) { } }; Runnable runnable2 = () -> { try { TimeUnit.SECONDS.sleep(15); } catch (InterruptedException e) { log.info("runnable2 被中断"); throw new RuntimeException(e); } }; EXECUTOR_SERVICE.submit(runnable1); EXECUTOR_SERVICE.submit(runnable2); TimeUnit.SECONDS.sleep(3); EXECUTOR_SERVICE.shutdownNow(); TimeUnit.SECONDS.sleep(3); log.info("活跃线程数" + EXECUTOR_SERVICE.getActiveCount()); if (EXECUTOR_SERVICE.isTerminated()) { log.info("线程池终止"); } } }
3 合理配置线程池
想要合理配置线程池,得先分析任务特性:
- 任务性质:CPU密集型任务、IO密集型任务和混合型任务
- 任务优先级:高、中和低。
- 任务执行时间:长、中和短。
- 任务依赖性:是否依赖其他系统资源,如数据库连接、网络连接等。 有以下建议:
- 性质不同的任务,可以分开使用不同的线程池。
- CPU密集型任务应配置尽可能小的线程,如配置CPU数+1个线程的线程池;
- IO密集型任务,线程并不是一直在占用CPU,应配置尽可能多的线程,如2倍cpu数;
- 优先级不同的任务,可以使用PriorityBlockingQueue,它可以让优先级高的任务先执行。
- 依赖于其他系统资源的任务,往往CPU空闲时间越长,可以设置更大的线程数。
- 建议使用有界队列,且拒绝策略能够给出提示或告警,这将增加线程池的稳定性和预警能力。
4 线程池监控
监控线程池,方便在出现问题时能够快速定位和处理。可调用ThreadPoolExecutor的这些方法:
- getTaskCount:已提交任务的大致总数;
- getCompletedTaskCount:线程池已完成的任务数量,是个近似值。
- getLargestPoolSize:线程池曾经创建过的最大线程数量,判断线程池是否曾经满过。
- getPoolSize:线程池的线程数量,即
ThreadPoolExecutor.workers.size()
; - getActiveCount:获取活动的线程数。 此外,通过继承ThreadPoolExecutor来自定义线程池,重写beforeExecute、afterExecute和terminated方法,在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。
AI 提问
标签: