系列文章:
- Java多线程复习与巩固(一)–线程基本使用
- Java多线程复习与巩固(二)–线程相关工具类的使用
- Java多线程复习与巩固(三)–线程同步
- Java多线程复习与巩固(四)–synchronized的实现
- Java多线程复习与巩固(五)–生产者消费者问题(第一部分)
- Java多线程复习与巩固(六)–线程池ThreadPoolExecutor详解
- Java多线程复习与巩固(七)–任务调度线程池ScheduledThreadPoolExecutor
- Java多线程复习与巩固(八)–原子性操作与原子变量
- Java多线程复习与巩固(九)–volatile关键字与CAS操作
- ThreadPoolExecutor最佳实践–如何选择线程数
- ThreadPoolExecutor最佳实践–如何选择队列
1. 为什么要使用线程池
线程创建与销毁都耗费时间,对于大量的短暂任务如果仍使用“创建->执行任务->销毁”的简单模式,将极大地降低线程的使用效率(一个线程仅仅处理一个短暂的任务就被销毁了)。在这种情况下,为了提高线程的使用效率,我们使用缓存池的策略让线程执行任务后不立即销毁而是等待着处理下一个任务。
2. 使用Executors工具类创建线程池
Executors是线程池框架提供给我们的创建线程池的工具类,它里面提供了以下创建几类线程池的方法。
// 创建固定线程数量的线程池
public static ExecutorService newFixedThreadPool();
// 创建单个线程的线程池(本质上就是容量为1的FixedThreadPool)
public static ExecutorService newSingleThreadExecutor();
// 创建无数量限制可自动增减线程的线程池
public static ExecutorService newCachedThreadPool();
// 创建(可计划的)任务延时执行线程池
public static ScheduledExecutorService newScheduledThreadPool();
// 单线程版的任务计划执行的线程池
public static ScheduledExecutorService newSingleThreadScheduledExecutor();
通过查看这几个方法的源码发现:前三个方法new了ThreadPoolExecutor
对象,而后面两个方法new了ScheduledThreadPoolExecutor
对象。
整个线程池框架的类继承图如下,其中ThreadPoolExecutor是本文的核心,ScheduleThreadPoolExecutor
将放到后一篇文章中讲。
JDK文档建议一般情况使用Executors去创建线程池
其中三个核心接口的方法如下:
3. 构造ThreadPoolExecutor对象
java.util.concurrent.ThreadPoolExecutor
类是线程池中最核心的类之一,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面分析一下ThreadPoolExecutor类的核心源码的具体实现。
在ThreadPoolExecutor类中提供了四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//代码省略
}
...
}
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService(实现ExecutorService接口)类,并提供了四个构造器,前三个构造器最终都会辗转调用第四个构造器。
下面解释一下第四个构造器中各个参数的含义:
-
corePoolSize:核心池的大小。
- 核心池中的线程会一致保存在线程池中(即使线程空闲),除非调用
allowCoreThreadTimeOut
方法允许核心线程在空闲后一定时间内销毁,该时间由构造方法中的keepAliveTime
和unit
参数指定; - 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了
prestartAllCoreThreads()
或者prestartCoreThread()
方法,从这两个方法的名字就可以看出,是**“预创建线程”**的意思,即在没有任务到来之前就创建corePoolSize个线程(prestartAllCoreThreads)或者一个线程(prestartCoreThread);
- 核心池中的线程会一致保存在线程池中(即使线程空闲),除非调用
-
maximumPoolSize:线程池允许的最大线程数。这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。
-
默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把新加入的任务放到缓存队列当中,缓存队列由构造方法中的
workQueue
参数指定,如果入队失败(队列已满)则尝试创建临时线程,但临时线程和核心线程的总数不能超过maximumPoolSize,当线程总数达到maximumPoolSize后会拒绝新任务;所以有两种方式可以让任务绝不被拒绝:① 将maximumPoolSize设置为
Integer.MAX_VALUE
(线程数不可能达到这个值),CachedThreadPool
就是这么做的;② 使用无限容量的阻塞队列(比如LinkedBlockingQueue),所有处理不过来的任务全部排队去,
FixedThreadPool
就是这么做的。
-
-
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。
- 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用——当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会被销毁,直到线程池中的线程数不超过corePoolSize。但是如果调用了
allowCoreThreadTimeOut(true)
方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用——当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会被销毁,直到线程池中的线程数不超过corePoolSize。但是如果调用了
-
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒
并发库中所有时间表示方法都是以
TimeUnit
枚举类作为单位 -
workQueue:一个阻塞队列(BlockingQueue接口的实现类),用来存储等待执行的任务,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue // 数组实现的阻塞队列,数组不支持自动扩容。所以当阻塞队列已满 // 线程池会根据handler参数中指定的拒绝任务的策略决定如何处理后面加入的任务 LinkedBlockingQueue // 链表实现的阻塞队列,默认容量Integer.MAX_VALUE(不限容), // 当然也可以通过构造方法限制容量 SynchronousQueue // 零容量的同步阻塞队列,添加任务直到有线程接受该任务才返回 // 用于实现生产者与消费者的同步,所以被叫做同步队列 PriorityBlockingQueue // 二叉堆实现的优先级阻塞队列 DelayQueue // 延时阻塞队列,该队列中的元素需要实现Delayed接口 // 底层使用PriorityQueue的二叉堆对Delayed元素排序 // ScheduledThreadPoolExecutor底层就用了DelayQueue的变体"DelayWorkQueue" // 队列中所有的任务都会封装成ScheduledFutureTask对象(该类已实现Delayed接口)
有关BlockingQueue的内容可以参考《Java集合框架总结与巩固》
-
threadFactory:线程工厂,主要用来创建线程;默认情况都会使用Executors工具类中定义的默认工厂类
DefaultThreadFactory
。可以实现ThreadFactory接口来自己控制创建线程池的过程(比如设置创建线程的名字、优先级或者是否为Deamon守护线程) -
handler:表示当拒绝处理任务时的策略,有以下四种取值(默认为AbortPolicy):
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
可通过实现RejectedExecutionHandler接口来自定义任务拒绝后的处理策略
4. 线程池的状态转换
ThreadPoolExecutor类中有一个ctl
属性,该属性是AtomicInteger类型,本质上就是32bit的int类型。这个32bit字段中存储了两个数据:
其中三个高字节位存储了线程池当前的运行状态,线程池状态有以下几个:
// runState is stored in the high-order bits
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;
- RUNNING:接受新任务并处理队列中的任务
- SHUTDOWN:不接受新任务但处理队列中的任务
- STOP:不接受新任务也不处理队列中的任务并终断正在处理中的任务
- TIDYING:所有任务已经终止,workerCount等于0,线程池切换到TIDYING后将会执行
terminated()
钩子方法 - TERMINATED:
terminated()
方法已执行完成
整个过程的状态转换图如下:
我们可以调用的两个改变线程池状态的方法分别是:
// 进入SHUTDOWN状态
public void shutdown();
// 进入STOP状态
public List<Runnable> shutdownNow();
另外ThreadPoolExecutor提供了一些方法来查询这些状态:
// 非运行状态:SHUTDOWN,STOP,TIDYING,TERMINATED
public boolean isShutdown() {
return ! isRunning(ctl.get());
}
// 正在终止状态:SHUTDOWN,STOP,TIDYING
public boolean isTerminating() {
int c = ctl.get();
return ! isRunning(c) && runStateLessThan(c, TERMINATED);
}
// 终止状态:TERMINATED
public boolean isTerminated() {
return runStateAtLeast(ctl.get(), TERMINATED);
}
5. 任务的提交
任务提交主要有三种方式:
- execute(Runnable command):定义在Executor接口中
- submit的三个重载方法:定义在ExecutorService接口中
- invoke(invokeAll,invokeAny)提交方式:定义在ExecutorService接口中
5.1 submit提交方式
submit方法的实现源码在ThreadPoolExecutor的基类AbstractExecutorService中:
// 将Runnable和Callable包装成FutureTask对象
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <