Java多线程(8)----Executor框架

1,概述

本文中涉及一些与线程池相关的概念,可以先看这个

Java中,使用线程来异步执行任务。如果为每一个任务都分配一个新线程来执行,这些线程的创建与销毁都将会消耗大量的计算资源。这种策略可能会使处于高负荷状态的应用最终崩溃

Java的线程既是工作单元,也是执行机制。从JDK5开始,把工作单元与工作机制分离开来:工作单元分为RunnableCallable,执行机制则由Executor框架提供。

HotSpot虚拟机的线程模型中,Java线程会被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作线程;Java线程终止时,这个操作系统线程也会被回收,通常操作系统会调度所有线程并将它们分配给可用的CPU

而在上层,Java多线程程序会将应用分解为若干个任务,然后通过用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;而在底层,操作系统将这些线程映射到硬件处理器上。相关的示意图如下所示:

在这里插入图片描述

上图可以这样理解:Java多线程程序将应用分解为若干个任务,任务被包装成Runnable或者Callable,交由Executor框架;Executor框架会将这些任务传给线程池进行执行;而线程池中的每个线程都有一个操作系统线程与之对应,操作系统则通过线程调度程序将这些操作系统线程交由CPU执行。

2,Executor框架的结构

  • 任务: 包括被执行任务需要执行的接口:Runnable接口或Callable接口
  • 任务的执行:包括任务执行机制的核心接口:Executor,以及继承自Executor接口的ExecutorService接口(两个关键类:ThreadPoolExecutorScheduledThreadPoolExecutor
  • 异步计算的结果:任务执行完后返回的结果,包括接口Future和实现Future接口的FutureTask

关于Exexutor框架包含的主要的类和接口如下图所示:

在这里插入图片描述

Executor框架的使用示意图:

在这里插入图片描述

  1. 主线程首先创建实现Runnable或者Callable接口的任务对象。工具类Executors可以将Runnable对象封装成为一个Callable对象。值得一提的是:因为FutureTask也实现了Runnable接口,所以也可以创建一个FutureTask交给ExecutorService执行
  2. Runnable对象直接交给ExecutorService执行(调用ExecutorService.execute)或者把Runnable / Callable提交给Executor执行(调用ExecutorService.submit(Runnable) 或者ExecutorService.submit(Callable)
  3. 如果执行ExecutorService.submit(...) 那么ExecutorService会返回一个实现了Future接口的对象(到目前为止返回的是FutureTask,但在源代码中,返回值的类型是Future,所以需要注意)
  4. 主线程执行FutureTask.get() 等到任务执行完成,或者可以执行FutureTask.cancel(...)取消任务的执行

3,Executor框架成员

Executor框架的主要成员有:

  • Runnable接口
  • Callable接口
  • Executors
  • ThreadPoolExecutor
  • ScheduledThreadPoolExecutor
  • Future接口

3.1,Runnable&Callable

Runnable接口与Callable接口的实现类,都可以被ThreadPoolExecutorScheduledThreadPoolExecutor执行。它们的区别在于: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

ExecutorsExecutor框架中扮演的时线程工厂的角色,可以通过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()方法将要调用的Handler
  • keepAliveTime:创建出来的工作线程(非核心线程池里面的线程)空闲多久之后将会被销毁

工作原理

值得一提的是,线程池的基本工作原理:

  1. 当有任务进来时,判断核心线程池里面的线程数是否达到corePoolSize指定的数量
  2. 如果未达到,那么就创建核心线程,并执行任务(这一步被称为线程池的预热)
  3. 如果达到,那么就将其加入任务队列;每当有新的任务进来时,就将任务加入任务队列中
  4. 当任务队列满了之后,线程池判断此时新创建线程是否会导致线程池内的线程数量大于maximumPoolSize指定的数量
  5. 如果不会,那么创建工作线程并从任务队列中拉取任务执行
  6. 如果会,那么执行拒绝策略

3.3.1,FixedThreadPool

FixedThreadPool被称为可重用固定线程数的线程池。相关的创建方法:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

线程池的corePoolSizemaximumPoolSize都被设置为nThreads,并且任务队列选择的是LinkedBlockingQueue,队列的容量为Integer.MAX_VALUE,也就是无界队列。

这就导致了,FixedThreadPool的线程数量达到corePoolSize之后,新来的任务将会进入任务队列中等待(实际上,当选择的任务队列是无界队列之后,maximumPoolSize这一参数就已经失效了;再加上corePoolSize和maximumPoolSize数值相同,keepAliveTime也将是一个无效参数)

而且由于是无界队列,所以也不会触发线程池的拒绝策略

FixedThreadPool的执行流程如图所示:

在这里插入图片描述

  1. 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务
  2. 在线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue
  3. 当线程执行完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>()));
    }

SingleThreadExecutorcorePoolSizemaximumPoolSize被设置为1,其他参数跟FixedThreadPool相同。使用LinkedBlockingQueue作为任务队列带来的影响同FixedThreadPool相同。

相关的运行示意图如下:

在这里插入图片描述

  1. 如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程)那么创建一个新线程来执行
  2. 在线程完成预热之后(当前线程池中有一个运行的线程),将任务加入LinkedBlockingQueue
  3. 线程执行完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秒后将会被终止

值得注意的是:CachedThreadPoolSynchronousQueue是无容量的阻塞队列,并且maximumPoolSizeInteger.MAX_VALUE,意味着当向线程池提交任务的速度快于线程池执行任务的速度,CachedThreadPool会不断的创建新线程。极端情况下会导致因为创建过多的线程而耗尽CPU和内存资源

相关的执行示意图如下:

在这里插入图片描述

  1. 首先执行SynchronousQueue.offer(Runnable task)。如果当前有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行poll操作配对成功,主线程会把任务交给空闲线程执行,execute()执行完成;否则执行步骤2
  2. 当初始maximumPool为空,或者没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),这时候CachedThreadPool会创建一个新的线程来执行任务,execute()执行完成
  3. 当步骤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的执行示意图如下所示:
在这里插入图片描述

  1. 当调用ScheduledThreadPoolExecutorscheduleAtFixedRate()或者scheduleWithFixedDelay(),会向ScheduledThreadPoolExecutorDelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask
  2. 线程池的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务

为了实现周期性的执行任务,ScheduledThreadPoolExecutorThreadPoolExecutor做出了如下的修改:

  • 使用DelayQueue作为任务队列
  • 获取任务的方式不同
  • 执行周期任务后,增加了额外的处理

3.4.2,ScheduledThreadPoolExecutor的实现

ScheduledThreadPoolExecutor会将待调度的任务(ScheduledFutureTask)放到一个DelayQueue

每个ScheduledFutureTask包含三个成员变量:

  • time:这个任务将要被执行的具体时间
  • sequenceNumber:这个任务被加入到线程池中的序号
  • period:任务执行的间隔周期

DelayQueue封装了一个PriorityQueue(优先级队列),这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时按照time从小到大的顺序,时间早的会被放到队头方向,时间相同的话;会根据sequenceNumber,将序号小的排到前面。

ScheduledThreadPoolExecutor的执行示意图:

在这里插入图片描述

  1. 线程1从DelayQueue中获取已经到期的ScheduledThreadFutureTask(通过执行DelayQueue.take()进行获取)。到期任务指的是ScheduledFutureTasktime小于等于当前时间
  2. 线程1执行这个ScheduledFutureTask
  3. 线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间
  4. 线程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

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值