1,概述
本文中涉及一些与线程池相关的概念,可以先看这个
在Java中,使用线程来异步执行任务。如果为每一个任务都分配一个新线程来执行,这些线程的创建与销毁都将会消耗大量的计算资源。这种策略可能会使处于高负荷状态的应用最终崩溃
Java的线程既是工作单元,也是执行机制。从JDK5开始,把工作单元与工作机制分离开来:工作单元分为Runnable和Callable,执行机制则由Executor框架提供。
在HotSpot虚拟机的线程模型中,Java线程会被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作线程;Java线程终止时,这个操作系统线程也会被回收,通常操作系统会调度所有线程并将它们分配给可用的CPU。
而在上层,Java多线程程序会将应用分解为若干个任务,然后通过用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;而在底层,操作系统将这些线程映射到硬件处理器上。相关的示意图如下所示:

上图可以这样理解:Java多线程程序将应用分解为若干个任务,任务被包装成Runnable或者Callable,交由Executor框架;Executor框架会将这些任务传给线程池进行执行;而线程池中的每个线程都有一个操作系统线程与之对应,操作系统则通过线程调度程序将这些操作系统线程交由CPU执行。
2,Executor框架的结构
- 任务: 包括被执行任务需要执行的接口:
Runnable接口或Callable接口 - 任务的执行:包括任务执行机制的核心接口:
Executor,以及继承自Executor接口的ExecutorService接口(两个关键类:ThreadPoolExecutor和ScheduledThreadPoolExecutor) - 异步计算的结果:任务执行完后返回的结果,包括接口
Future和实现Future接口的FutureTask类
关于Exexutor框架包含的主要的类和接口如下图所示:

Executor框架的使用示意图:

- 主线程首先创建实现
Runnable或者Callable接口的任务对象。工具类Executors可以将Runnable对象封装成为一个Callable对象。值得一提的是:因为FutureTask也实现了Runnable接口,所以也可以创建一个FutureTask交给ExecutorService执行 - 将
Runnable对象直接交给ExecutorService执行(调用ExecutorService.execute)或者把Runnable / Callable提交给Executor执行(调用ExecutorService.submit(Runnable)或者ExecutorService.submit(Callable)) - 如果执行
ExecutorService.submit(...)那么ExecutorService会返回一个实现了Future接口的对象(到目前为止返回的是FutureTask,但在源代码中,返回值的类型是Future,所以需要注意) - 主线程执行
FutureTask.get()等到任务执行完成,或者可以执行FutureTask.cancel(...)取消任务的执行
3,Executor框架成员
Executor框架的主要成员有:
Runnable接口Callable接口ExecutorsThreadPoolExecutorScheduledThreadPoolExecutorFuture接口
3.1,Runnable&Callable
Runnable接口与Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。它们的区别在于:Runnable不会返回结果,而Callable可以返回结果。
可以通过工厂类Executors把一个Runnable包装成一个Callable:
public static Callable<Object> callable(Runnable task) //假设这个方法返回的是对象Callable1
或是将一个Runnable和一个待返回的结果包装成一个Callable:
public static <T> Callable<T> callable(Runnable task, T result)//假设这个方法返回的是对象Callable2
当我们把Callable交给线程池执行时,执行submit(...)会返回一个FutureTask对象,主线程可以通过FutureTask.get()等待任务执行完成,执行完成后FutureTask.get()将返回结果。如果是向线程池提交对象Callable1的话,那么将返回null;如果向线程池提交对象Callable2,那么将返回result对象。
3.2,Executors
Executors在Executor框架中扮演的时线程工厂的角色,可以通过Executors创建特定功能的线程池:
public class Executors {
...
//创建固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
...
//创建单个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
...
}
3.3,ThreadPoolExecutor
ThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建3种类型的ThreadPoolExecutor:
SingleThreadExecutor:单个线程的线程池FixedThreadPool:固定线程数的线程池CachedThreadPool:根据需要创建新线程的线程池
参数说明
创建线程池时需要传入的相关参数:
corePoolSize:核心线程池的大小maximumPoolSize:最大线程池的大小BlockingQueue:用来暂时保存任务的工作队列RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或者已经饱和之后,execute()方法将要调用的HandlerkeepAliveTime:创建出来的工作线程(非核心线程池里面的线程)空闲多久之后将会被销毁
工作原理
值得一提的是,线程池的基本工作原理:
- 当有任务进来时,判断核心线程池里面的线程数是否达到
corePoolSize指定的数量 - 如果未达到,那么就创建核心线程,并执行任务(这一步被称为线程池的预热)
- 如果达到,那么就将其加入任务队列;每当有新的任务进来时,就将任务加入任务队列中
- 当任务队列满了之后,线程池判断此时新创建线程是否会导致线程池内的线程数量大于
maximumPoolSize指定的数量 - 如果不会,那么创建工作线程并从任务队列中拉取任务执行
- 如果会,那么执行拒绝策略
3.3.1,FixedThreadPool
FixedThreadPool被称为可重用固定线程数的线程池。相关的创建方法:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
线程池的corePoolSize和maximumPoolSize都被设置为nThreads,并且任务队列选择的是LinkedBlockingQueue,队列的容量为Integer.MAX_VALUE,也就是无界队列。
这就导致了,当FixedThreadPool的线程数量达到corePoolSize之后,新来的任务将会进入任务队列中等待(实际上,当选择的任务队列是无界队列之后,maximumPoolSize这一参数就已经失效了;再加上corePoolSize和maximumPoolSize数值相同,keepAliveTime也将是一个无效参数)
而且由于是无界队列,所以也不会触发线程池的拒绝策略
FixedThreadPool的执行流程如图所示:

- 如果当前运行的线程数少于
corePoolSize,则创建新线程来执行任务 - 在线程池完成预热之后(当前运行的线程数等于
corePoolSize),将任务加入LinkedBlockingQueue - 当线程执行完1中的任务之后,会循环地从任务队列中获取任务进行执行
3.3.2,SingleThreadExecutor
SingleThreadExecutor是使用某个worker线程的Executor,相关的创建方法:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1,其他参数跟FixedThreadPool相同。使用LinkedBlockingQueue作为任务队列带来的影响同FixedThreadPool相同。
相关的运行示意图如下:

- 如果当前运行的线程数少于
corePoolSize(即线程池中无运行的线程)那么创建一个新线程来执行 - 在线程完成预热之后(当前线程池中有一个运行的线程),将任务加入
LinkedBlockingQueue - 线程执行完1中的任务之后,会循环的从
LinkedBlockingQueue获取任务来执行
3.3.3,CachedThreadPool
CachedThreadPool是一个会根据需要创建新线程的线程池,相关的创建代码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
corePoolSize被设置为0,maximumPoolSize被设置为Integer.MAX_VALUE,意味着线程池允许创建无限多的线程
任务队列使用 SynchronousQueue,这是一种无容量的阻塞队列,其作用为:每个插入操作必须等待另一个线程的对应移除操作(每当有任务进来时,不会贮存在队列中,而是必须有一个线程来领取这个任务)。
keepAliveTime被设置为60,意味着CachedThreadPool内的空闲线程等待新任务的最长时间为60秒,60秒后将会被终止
值得注意的是:CachedThreadPool的SynchronousQueue是无容量的阻塞队列,并且maximumPoolSize为Integer.MAX_VALUE,意味着当向线程池提交任务的速度快于线程池执行任务的速度,CachedThreadPool会不断的创建新线程。极端情况下会导致因为创建过多的线程而耗尽CPU和内存资源
相关的执行示意图如下:

- 首先执行
SynchronousQueue.offer(Runnable task)。如果当前有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行poll操作配对成功,主线程会把任务交给空闲线程执行,execute()执行完成;否则执行步骤2 - 当初始
maximumPool为空,或者没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),这时候CachedThreadPool会创建一个新的线程来执行任务,execute()执行完成 - 当步骤2中创建的线程执行完任务之后,会再次执行
SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),这个方法会让线程等待60秒钟,如果60秒钟内主线程提交了一个新任务过来,将由这个空闲线程执行新任务;否则空闲线程将终止。由于空闲60秒的线程将会被终止,所以长期保持空闲的CachedThreadPool不会使用任何资源
CachedThreadPool中的任务传递示意图如下:

3.4,ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,用于在给定的延迟时间后运行任务,或者是定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但比其功能更强大、更灵活;Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor则可以指定多个对应的后台线程数
3.4.1,ScheduledThreadPoolExecutor运行机制
ScheduledThreadPoolExecutor的执行示意图如下所示:

- 当调用
ScheduledThreadPoolExecutor的scheduleAtFixedRate()或者scheduleWithFixedDelay(),会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask - 线程池的线程从
DelayQueue中获取ScheduledFutureTask,然后执行任务
为了实现周期性的执行任务,ScheduledThreadPoolExecutor对ThreadPoolExecutor做出了如下的修改:
- 使用
DelayQueue作为任务队列 - 获取任务的方式不同
- 执行周期任务后,增加了额外的处理
3.4.2,ScheduledThreadPoolExecutor的实现
ScheduledThreadPoolExecutor会将待调度的任务(ScheduledFutureTask)放到一个DelayQueue中
每个ScheduledFutureTask包含三个成员变量:
time:这个任务将要被执行的具体时间sequenceNumber:这个任务被加入到线程池中的序号period:任务执行的间隔周期
DelayQueue封装了一个PriorityQueue(优先级队列),这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时按照time从小到大的顺序,时间早的会被放到队头方向,时间相同的话;会根据sequenceNumber,将序号小的排到前面。
ScheduledThreadPoolExecutor的执行示意图:

- 线程1从
DelayQueue中获取已经到期的ScheduledThreadFutureTask(通过执行DelayQueue.take()进行获取)。到期任务指的是ScheduledFutureTask的time小于等于当前时间 - 线程1执行这个
ScheduledFutureTask - 线程1修改
ScheduledFutureTask的time变量为下次将要被执行的时间 - 线程1把这个修改
time之后的ScheduledFutureTask放回DelayQueue中(通过DelayQueue.add())
3.5,FutureTask
Future接口和实现Future接口的FutureTask类,代表异步计算的结果
FutureTask除了实现Future接口外,还实现了Runnable接口。因此可以将一个FutureTask交给Executor执行,也可以直接FutureTask.run()直接执行。根据FutureTask.run()的执行实际,FutureTask可以处于下面3中状态:
- 未启动:
FutureTask.run()未被执行,FutureTask处于未启动状态 - 已启动:
FutureTask.run()方法被执行过程中,FutureTask就处于已启动状态 - 已完成:
FutureTask.run()方法执行完后正常结束,或者被取消(执行FutureTask.cancel())又或者FutureTask.run()抛出异常或者异常结束,FutureTask处于已完成状态

在不同状态执行不同的方法会造成不同的结果:
FutureTask处于未启动或者已启动时,执行FutureTask.get()将导致线程阻塞- 当
FutureTask处于已完成状态,执行FutureTask.get()将会导致线程立即返回结果或者抛出异常 - 当
FutureTask处于未启动状态,执行FutureTask.cancel()将导致任务不会被执行 - 当
FutureTask处于已启动状态,执行FutureTask.cancel(true)将通过中断线程来试图停止任务;如果执行FutureTask.cancel(false)将不会对执行此任务的线程造成影响 - 当
FutureTask处于已完成状态时,执行FutureTask.cancel(...)将返回false


本文详细介绍了Java线程池的原理、Executor框架结构、Runnable与Callable的区别、Executors工厂类的作用、ThreadPoolExecutor的种类及其工作原理,以及FutureTask的角色。涵盖了线程池的创建、任务提交与结果获取等关键知识点。
----Executor框架&spm=1001.2101.3001.5002&articleId=125666700&d=1&t=3&u=28c82dc9368c4684a6f1c54dc34419a7)
1089

被折叠的 条评论
为什么被折叠?



