背景
Java 底层提供了 Thread
类支持开发人员手动创建线程执行任务,但是这种操作会存在以下问题:
- 每次都要重新创造和回收
Thread
对象,性能较差。如果并发线程数较大,且任务执行时间较短,频繁创建和回收线程对象会大大降低系统的效率 - 线程缺乏统一管理,不同业务之间可能会无限制的创建线程,相互之间竞争资源,容易导致系统资源占用过多,从而引起 OOM 或程序奔溃
- 功能比较单一,缺乏其他通用能力
在这样的背景下,结合池化思想,则可以通过线程池来优化设计。具体的优势在于:
- 通过重复利用已创建的线程,避免因频繁创建和回收线程对象而对系统造成的资源开销
- 提高任务响应速度,当任务到达后,可以立即使用已创建的线程执行任务
- 统一管理线程的分配、调度和监控,可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
使用线程池时场景,一般满足以下特征:
- 单个任务的处理时间比较短
- 需要处理的任务数量特别大
Executor 框架
Executor
是 Java 中一个经典的多线程任务管理框架,其主要提供了任务、任务执行线程池、任务执行结果三个组件,每个组件的具体定义与含义如下:
组件 | 含义 |
---|---|
任务 | Executor 框架提供了 Runnable 接口和 Callable 接口,任务需要实现这两个接口才能被线程执行 |
任务执行线程池 | Executor 框架提供了接口 Executor 和继承于 Executor 的 ExecutorService 接口来定义任务执行机制。Executor 框架中的线程池类 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 均实现了ExecutorService 接口 |
任务执行结果 | Executor 框架提供了 Future 接口和实现了 Future 接口的 FutureTask 类来定义任务执行结果。调用 ExecutorService 的 submit() 方法后会返回一个 Future 对象。 |
基本流程
- 主线程创建实现了
Runnable
或者Callable
接口的任务对象; - 把创建完成的
Runnable
对象或者Callable
对象提交给ExecutorService
执行execute()
或者submit()
方法; - 如果执行的是
submit()
方法,则ExecutorService
会返回一个实现了Future
接口的对象; - 最后主线程可以执行
Future
接口对象的get()
方法来等待任务执行完成,也可以执行cancel()
方法来取消任务的执行,通过参数boolean mayInterruptIfRunning
来控制是否中断正在执行中的任务。
设计思想
- 顶层接口
Executor
提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable
对象,将任务的运行逻辑提交到执行器Executor
中,由Executor
框架完成线程的调配和任务的执行部分。 ExecutorService
接口增加了一些能力:- 扩充执行任务的能力,补充可以为一个或一批异步任务生成
Future
的方法; - 提供了管控线程池的方法,比如停止线程池的运行。
- 扩充执行任务的能力,补充可以为一个或一批异步任务生成
AbstractExecutorService
则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。- 最下层的实现类
ThreadPoolExecutor
实现最复杂的运行部分,一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。