Java中Executor框架和线程池的介绍与使用
首先声明该内容是基于Java 8 进行介绍和讲解
前提概念
什么是Executor框架?
在Java 5之后,并发编程引入了一堆新的启动、调度和管理线程的API,而这些API的整体框架就是Executor框架
- Executor的内部实现主要就是线程池来实现,既Executor是通过线程池的方式来控制上层的调度的。所以Executor一定角度上扮演者线程工厂的角色,我们可以通过Executor框架创建特定功能的线程池。
为什么引入Executor框架?
为什么要引入Executor框架呢?那我们就需要来讨论一下new Thread()的缺点:
- 每次new Thread()和destroy一个Thread都会耗费一定的系统资源 ,比如我们为每个任务都创建一个新线程来执行,这就会消耗大量的CPU资源来创建和消耗线程,同时这种策略也会使处于高负荷状态的应用崩溃
- 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。
- 同时new Thread()的方式不利于扩展,比如如定时执行、定期执行、线程中断
总之就是,无人管理,不利于拓展,耗费资源,所以为了解决这一个问题,JDK1.5开始引入了Executor框架用于管理线程和调度线程,可以参考Executor框架的两级调度模型
Executor框架的两级调度模型
在HotSpot 虚拟机线程模型中,Java线程(java.lang.Thread)被一对一的映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程。但该Java线程终止时,这个本地操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU.
而有了Executor框架之后,我们可以将线程调度分为上下两层:
- 上层是Java层面上的应用线程层
- 下层是本地操作系统层面上的底层线程层
在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在下层,操作系统内核将这些线程映射到硬件处理器上。如下图:

从图中(图中下面是CPU,非任务,画错了,Sorry),我们看出,应用程序通过Executor框架来控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。
Executor框架的组织架构图
Executor框架的组织架构图如下:

从架构图,我们可以看出:
- Executor是一个顶级的接口,接口只有execute一个方法
- ExecutorService是Executor的子接口,拥有更多需要用的的方法规范
- ExecutorService拥有两个重要实现类ThreadPoolExecutor和ScheuledThreadPoolExecutor
- ScheuledThreadPoolExecutor实际最后也是通过ThreadPoolExecutor去实现的
Executor框架的主要成员有:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
- Future接口
- Runnable接口
- Callable接口
- Executors工具类
Executors工具类创建的4种线程池
从上面的概念中,我们知道了Executor框架实际是依靠线程池来控制上层的线程调度的。所以线程池自然是重点。而Executor的线程池一般都是通过Executors工具类来创建的,大致可以分为4种:
- FixedThreadPool
- SingleThreadPool
- CacheThreadPool
- ScheduleThreadPoll
而线程池又可以根据Executor大致分为两种:
- ThreadPoolExcutor(FixedThreadPool,SingleThreadPool,CacheThreadPool)
- ScheduleThreadExcutor(ScheduleThreadPool)
FixedThreadPool
Executors的方法:
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
使用例子:
ExecutorService threadPool = Executors.newFixedThreadPool(5);
FixedThreadPool的特点:
- FixedThreadPool线程池是一个线程数固定的线程池,比如
Executors.newFixedThreadPool(5)
的意思就是创建一个线程数固定为5个的线程池。 - FixedThreadPool线程池适用于为满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器
- 使用链式有界阻塞列队LinkedBlockingQueue来存放任务,既当工作线程已满,没有空闲线程时,待执行任务全部都放在一个LinkedBlockingQueue队列中,这是一个有界的阻塞队列,如果队列已满,生成任务的生成者将被阻塞
SingleThreadPool
Executors的创建方法:
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
使用例子:
ExecutorService threadPool = Executors.newSingleThreadPool(5);
SingleThreadPool的特点:
- SingleThreadPool是一个单线程的线程池,既整个线程池中,仅有一个线程
- SingleThreadPool适用于需要保证顺序地执行的各个任务且在任意时间点,不会有多个线程任务是活动的应用场景
- SIngleThreadPool使用的是LinkedBlockQueue链式有界阻塞队列来存放任务。如果队列已满,生成任务的生成者将被阻塞
CacheThreadPool
Executors的创建方法:
public static ExecutorService newCachedThreadPool()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
使用例子:
ExecutorService threadPool = Executors.newSingleThreadPool(5);
CacheThreadPool的特点:
- CacheThreadPool是一个大小无界的线程池,既是一个按需创建线程的线程池
- 适用于执行很多的短期异步任务的小程序或负载较轻的服务器
- 使用SynchronousQueue不存放元素的阻塞队列来存放任务
ScheduleThreadPool
Executors的创建方法:
//01
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
//02
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
使用例子:
//01
ExecutorService threadPool = Executors.newScheduledThreadPool(5);
//02
ExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
ScheduleThreadPool有两种形式:
- 第一种是限定某个数量的线程池
- 另一种是单线程线程池
ScheduleThreadPool的特点:
- ScheduleThreadPool有两种形式,一种是类似于FixedThreadPool的限制线程数量的线程池,另一种是类似于SingleThreadPool的单线程线程池
- ScheduleThreadPool是属于ScheduleThreadPoolExecutor的,其他的3种是ThreadPoolExecutor
- ScheduleThreadPool是使用DelayedWorkQueue来存储任务的
小结:
- 虽然可以使用Executors工具类直接生产线程池,但是就像阿里巴巴规范所说的一样,我们不推荐Executors去生成线程池,而是通过new ThreadPoolExcutor()的方式。因为使用工具的线程池参数固定,不灵活,有一定的缺陷,为了规范,都统一使用new ThreadPoolExcutor()的方式,按需填写参数。
线程池的创建和使用
线程池的创建
new ThreadPoolExecutor(corePoolSize,maxinumPoolSize,keepAliveTime,milliseconds,
runnableTaskQueue,handle)
以上是创建一个线程的方法,new一个ThreadPoolExecutor,其中参数有6个:
- corePoolSize
线程池的基本大小,既该线程池初始化时拥有多少个存活线程。既比如我的核心线程池有5个线程。那个我任务只有2个,线程池也是存活5个线程。 - maxinumPoolSize
线程池的最大数量,线程池允许线程的最大数量。但如果使用了无界的阻塞队列,则这个参数就没有什么效果 - keepAliveTime
线程活动保持时间,线程池的工作线程空闲后,保持存活的时间,所以,如果任务很多,每个任务的执行时间比较短,可以调大时间,提高线程的利用率。 - milliseconds
线程活动保持时间的单位 - runnableTaskQueue
该线程池的存放任务的阻塞队列是那种 - handle
饱和策略,当队列和线程池都满了,说明线程池处于饱和状态,那么必须采用一种策略来处理新提交的任务。
JDK1.5的4种饱和策略
- AbortPolicy
如果队列满了,则直接抛出异常。是Executors生成线程池的默认方法 - CallerRunsPolicy
如果队列满了,则使用调用线程执行溢出的任务,即不抛弃任何一个任务 - DiscardOldesPolicy
丢弃队列中最老的的任务,插入新任务 - DiscardPlicy
如果队列满了,则丢弃所有新任务,直到队列有位置
向线程池提交任务
大致有两个方法
- execute()
- submit()
参数是任务,execute()用来提交没有返回值的任务,submit()方法用来提交带返回值的任务
关闭线程池
- shutdown
- shutdownNow
shutdown仅仅是设置运行标识为SHUTDOWN,正在运行的线程等待运行结束
shutdownNow则是设置运行标识为STOP,正在运行的线程也会中断掉
实现例子
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
8,
8,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(16),
new ThreadPoolExecutor.CallerRunsPolicy());