1.JAVA多线程(十八)Java多线程之Executor框架&ThreadPoolExecutor类
Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
在开发中如果需要创建线程可优先考虑使用Executor,无论你需要多线程还是单线程,Executor为你提供了很多其他功能,包括线程状态,生命周期的管理。Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。
1.1 无限制创建线程不足
在生产环境中,“为每个任务分配一个线程”这种方法存在一些缺陷,尤其是当需要创建大量线程时:
- 管理线程的生命周期开销非常高:管理这些线程的生命周期会明显增加 CPU 的执行时间,会消耗大量计算资源。
- 资源消耗:活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于可用的处理器的数量,那么有些线程将闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量线程在竞争CPU资源时还将产生其他的性能开销。如果你已经拥有足够多的线程使所有的CPU保持忙碌状态,那么再创建更多的线程反而会降低性能。
- 稳定性:创建线程的数量存在一个限制,这个限制将随着平台的不同而不同,并且受多个因素制约,包括jvm的启动参数、Thread构造函数中请求的栈大小,以及底层操作的限制等。如果超过了这个限制,那么很可能抛出OutOfMemoryError异常,这对于运行中的应用来说是非常危险的。
所有的这些因素都会导致系统吞吐量下降。线程池通过保持一些存活线程并重用这些线程来克服这个问题。当提交到线程池中的任务多于线程池最大任务数时,那些多余的任务将被放到一个队列中。 一旦正在执行的线程有空闲了,它们会从队列中取下一个任务来执行。JDK 中的 Executors中, 此任务队列是没有长度限制的。
1.2 Executor的实现关系图
Executor框架包括3大部分:
- 任务:包括被执行的任务需要实现的接口:Runable 接口、Callable接口;Runnable 接口或 Callable 接口 实现类都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。
- 任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口:ThreadPoolExecutor 和 ScheduledThreadPoolExecutor、ForkJoinPool;这里提了很多底层的类关系,但是,实际上我们需要更多关注的是 ThreadPoolExecutor 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的。
- 任务的异步计算结果:包括Future接口和实现Future接口的FutureTask类、ForkJoinTask类,都可以代表异步计算的结果。当我们把 Runnable接口 或 Callable 接口 的实现类提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。(调用 submit() 方法时会返回一个 FutureTask 对象)
Java优秀框架的设计思路,顶级接口-次级接口-虚拟实现类-实现类。
属性 | 说明 |
---|---|
Executor | 执行者,java线程池框架的最上层父接口,地位类似于spring的BeanFactry、集合框架的Collection接口,在Executor这个接口中只有一个execute方法,该方法的作用是向线程池提交任务并执行。 |
ExecutorService | 该接口继承自Executor接口,添加了shutdown、shutdownAll、submit、invokeAll等一系列对线程的操作方法,该接口比较重要,在使用线程池框架的时候,经常用到该接口。 |
AbstractExecutorService | 这是一个抽象类,实现ExecuotrService接口 |
ThreadPoolExecutor | 这是Java线程池最核心的一个类,该类继承自AbstractExecutorService,主要功能是创建线程池,给任务分配线程资源,执行任务。 |
ScheduledExecutorSerivce | 提供了另一种线程池:延迟执行和周期性执行的线程池。 |
ScheduledThreadPoolExecutor | 提供了另一种线程池:延迟执行和周期性执行的线程池。 |
Executors | 这是一个静态工厂类,该类定义了一系列静态工厂方法,通过这些工厂方法可以返回各种不同的线程池。 |
1.3 ThreadPoolExecutor 类
- 线程池实现类 ThreadPoolExecutor 是 Executor 框架最核心的类。
ThreadPoolExecutor 类中提供的四个构造方法,直接看参数最多的构造方法,其余三个都是在这个构造方法的基础上产生。源代码如下:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize, // 线程池的核心线程数量,即使空闲时仍保留在池中的线程数,除非设置 allowCoreThreadTimeOut
int maximumPoolSize, // 池中允许的最大线程数
long keepAliveTime, // 当线程数大于内核时,这是多余的空闲线程在终止前等待新任务的最大时间。
TimeUnit unit, // keepAliveTime参数的时间单位
BlockingQueue<Runnable> workQueue, // 用于在执行任务之前使用的队列。 这个队列将仅保存execute方法提交的Runnable任务。
ThreadFactory threadFactory, // 线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler // 拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null