前言
创建一个新的线程可以通过继承Thread类或者实现Runnable接口来实现,这两种方式创建的线程在运行结束后会被虚拟机销毁,进行垃圾回收,如果线程数量过多,频繁的创建和销毁线程会浪费资源,降低效率。而线程池的引入就很好解决了上述问题,线程池可以更好的创建、维护、管理线程的生命周期,做到复用,提高资源的使用效率,也避免了开发人员滥用new关键字创建线程的不规范行为。
说明:阿里开发手册中明确指出,在实际生产中,线程资源必须通过线程池提供,不允许在应用中显式的创建线程。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
接下来主要对Java中线程池核心实现类ThreadPoolExecutor核心参数及工作原理、Executors工具类等,进行说明。
ThreadPoolExecutor
ThreadPoolExecutor是线程池的核心实现类,在JDK1.5引入,位于java.util.concurrent包,由Doug Lea完成。
Executor接口
Executor是线程池的顶层接口,JDK1.5开始引入了,位于java.util.concurrent
包。
public interface Executor {
// 该接口中只定义了一个Runnable作为入参的execute方法
void execute(Runnable command);
}
查看Executor接口的实现类图
Executor
线程池相关顶级接口,它将任务的提交与任务的执行分离开来ExecutorService
继承并扩展了Executor接口,提供了Runnable、FutureTask等主要线程实现接口扩展ThreadPoolExecutor
是线程池的核心实现类,用来执行被提交的任务ScheduledExecutorService
继承ExecutorService
接口,并定义延迟或定期执行的方法ScheduledThreadPoolExecutor
继承ThreadPoolExecutor
并实现了ScheduledExecutorService
接口,是延时执行类任务的主要实现
生命周期
线程存在生命周期,同样线程池也有生命周期,源码中定义了五种状态。
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
关于线程池状态间转换如下图所示:
构造方法
如何利用ThreadPoolExecutor创建一个线程池,查看其构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor包含了7个核心参数,参数含义:
- corePoolSize:核心线程池的大小
- maximumPoolSize:最大线程池的大小
- keepAliveTime:当线程池中线程数大于corePoolSize,并且没有可执行任务时大于corePoolSize那部分线程的存活时间
- unit:keepAliveTime的时间单位
- workQueue:用来暂时保存任务的工作队列
- threadFactory:线程工厂提供线程的创建方式,默认使用Executors.defaultThreadFactory()
- handler:当线程池所处理的任务数超过其承载容量或关闭后继续有任务提交时,所调用的拒绝策略
核心参数
ThreadPoolExecutor中包含了七大核心参数,如果需要对线程池进行定制化操作,需要对其中比较核心的参数进行一定程度的认识。
corePoolSize
ThreadPoolExecutor会根据corePoolSize和maximumPoolSize在构造方法中设置的边界值自动调整池大小,也可以使用setCorePoolSize和setMaximumPoolSize动态更改,关于线程数量的自动调整分为以下两种场景:
- 线程数量小于corePoolSize
当在线程池中提交了一个新任务,并且运行的线程少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求。
- 线程数量介于corePoolSize和maximumPoolSize之间
如果运行的线程数多于corePoolSize但少于maximumPoolSize,则仅当队列已满时才会创建新线程。
如果corePoolSize和maximumPoolSize相同,那么可以创建一个固定大小的线程池。如果maximumPoolSize被设置为无界值(Integer.MAX_VALUE),在资源允许的前提下,意味着线程池允许容纳任意数量的并发任务。
默认情况下,即使是核心线程也会在新任务到达时开始创建和启动,如果使用非空队列创建线程池池,可以通过重写prestartCoreThread或prestartAllCoreThreads方法动态覆盖,进行线程预启动。
在实际开发中,如果需要自定义线程数量,可以参考以下公式:
其中参数含义如下:
- _Ncpu_是处理器的核数目,可以通过Runtime.getRuntime().availableProcessors()获得
- _Ucpu_是期望的CPU利用率,介于0-1之间
- W/C是等待时间与计算时间的比率
keepAliveTime
keepAliveTime参数用来来设置空闲时间。如果池当前有多个corePoolSize线程,多余的线程如果空闲时间超过将会被终止,这种机制减少了在任务数量较少时线程池资源消耗。如果某个时间需要处理的任务数量增加,则将构造新线程。使用方法setKeepAliveTime可以动态更改参数值。
默认情况下,keep-alive策略仅适用于超过corePoolSize线程的情况,但是方法allowCoreThreadTimeOut也可用于将此超时策略应用于核心线程,只要 keepAliveTime值不为零即可。
workQueue
workQueue参数用来指定存放提交任务的队列,任何BlockingQueue都可以用来传输和保存提交的任务。关于队列大小与线程数量之间存在这样的关系:
- 如果线程数少于corePoolSize,对于提交的新任务会创建一个新的线程处理,并不会把任务放入队列
- 如果线程数介于corePoolSize和maximumPoolSize之间,新提交的任务会被放入阻塞队列中
- 如果线程池处于饱和状态,即无法创建线程也无法存放在阻塞队列,那么新任务将交由拒绝策略来处理
线程池中的常用阻塞队列一般包含SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue几种,它们都是BlockingQueue的实现类,下面进行简单介绍。
SynchronousQueue
SynchronousQueue并不能算得上一个真正的队列