众所周知,频繁地创建和销毁线程对于CPU来说,压力是很大的,而线程池通过复用线程,就可以避免线程频繁地创建和销毁。
Java 的 Executors 工具类中提供了 5 种类型的线程池创建方法,来看下它们的特点和适用场景。
固定大小线程池,特点是线程数固定,使用无界队列,适用于任务数量不均匀的场景、对内存压力不敏感但系统负载比较敏感的场景;
Cached 线程池,特点是不限制线程数,适用于要求低延迟的短期任务场景;
单线程线程池,就是一个线程的固定线程池,适用于需要异步执行但需要保证任务顺序的场景;
Scheduled 线程池,适用于定期执行任务场景,支持按固定频率定期执行和按固定延时定期执行两种方式;
工作窃取线程池,使用的是 ForkJoinPool,是固定并行度的多任务队列,适合任务执行时长不均匀的场景。
线程池除了工作窃取线程池外,都是通过 ThreadPoolExecutor 的不同初始化参数来创建的。阿里巴巴规范不推荐我们直接使用Executors类直接进行创建线程池,为的是我们能更好的掌握线程池的使用
corePoolSize:核心线程数,默认情况下核心线程会一直存活
maximumPoolSize:最大线程数,决定线程池最多可以创建的多少线程
keepAliveTime:设置线程空闲时间,
unit:空闲时间的单位,
workQueue:缓冲队列,ArrayBlockingQueue 是一个有界队列,就是指队列有最大容量限制;LinkedBlockingQueue 是无界队列,就是队列不限制容量;SynchronousQueue,是一个同步队列,内部没有缓冲区。
threadFactory:线程池工厂方法,线程工厂用来创建新线程,可以用来对线程的一些属性进行定制,例如线程的 group、线程名、优先级等。一般使用默认工厂类即可
handler:线程池满时的拒绝策略。Abort 策略在线程池满后,提交新任务时会抛出 RejectedExecutionException,这个也是默认的拒绝策略。Discard 策略会在提交失败时对任务直接进行丢弃。CallerRuns 策略会在提交失败时,由提交任务的线程直接执行提交的任务。DiscardOldest 策略会丢弃最早提交的任务。
向线程提交任务时可以使用 execute 和 submit,区别就是 submit 可以返回一个 future 对象,通过 future 对象可以了解任务执行情况,可以取消任务的执行,还可获取执行结果或执行异常。submit 最终也是通过 execute 执行的。
流程:
向线程池提交任务时,会首先判断线程池中的线程数是否大于设置的核心线程数,如果不大于,就创建一个核心线程来执行任务。
如果大于核心线程数,就会判断缓冲队列是否满了,如果没有满,则放入队列,等待线程空闲时执行任务。
如果队列已经满了,则判断是否达到了线程池设置的最大线程数,如果没有达到,就创建新线程来执行任务。
如果已经达到了最大线程数,则执行指定的拒绝策略。
固定大小线程池创建时核心和最大线程数都设置成指定的线程数,这样线程池中就只会使用固定大小的线程数。
队列使用无界队列 LinkedBlockingQueue。
Single 线程池就是线程数设置为 1 的固定线程池。
Cached 线程池的核心线程数设置为 0,最大线程数是 Integer.MAX_VALUE,主要是通过把缓冲队列设置成 SynchronousQueue,这样只要没有空闲线程就会新建。
Scheduled 线程池与前几种不同的是使用了 DelayedWorkQueue,这是一种按延迟时间获取任务的优先级队列。