Java线程池之Executor框架、四种常见的线程池

1. Executor框架简介
① Java任务调度的两级调度模型
  • 在HotSpot VM的线程模型中,Java线程(java.lang.Thread)被一对一的映射为本地操作系统线程:Java线程启动时会创建一个本地操作系统线程;该Java线程终止时,本地操作系统线程也会被回收。操作系统会调度所有线程并把他们分配给可用的CPU。
  • Java任务调度的两级调度模型:
  1. 在上层,应用程序的被分解为若干个任务,这些任务被用户级的调度器(Executor框架)映射为固定数量的线程。
  2. 在底层,操作系统内核将这些线程映射到CPU上。
  • 应用程序可以通过Executor框架控制上层的调度,而底层的调度由操作系统内核完成,不受应用程序的控制
    在这里插入图片描述
② Executor框架的结构

Executor框架主要由三部分组成:

  • 任务: 被执行任务需要实现Runnable接口或者Callable接口
  1. Runnable接口和Callable接口都可以被ThreadPoolExecutorScheduledThreadPoolExecutor执行。
  2. 两者的区别:
    ① Runnable接口不会返回结果,而Callable接口可以返回结果
    ② Runnable对象可以通过Executors工具类中的Executors.callable(Runnable task)或者Executors.callable(Runnable task, Object result)转化为Callable对象。
  • 任务的执行: 包括任务执行机制的核心接口 Executor ,以及继承自Executor 接口的ExecutorService接口Executor框架有两个关键类ThreadPoolExecutor类ScheduledThreadPoolExecutor类,实现了ExecutorService接口。
  1. 其中,ScheduledThreadPoolExecutor类继承ThreadPoolExecutor类,并实现ScheduledExecutorService接口
  2. ScheduledExecutorService接口实现ExecutorService接口
    在这里插入图片描述
  • 异步计算的结果: Future接口和实现了Future接口的FutureTask类
  1. 将实现Runnable接口或者Callable接口的任务对象(通过ExecutorService.submit()方法提交ThreadPoolExecutor或者ScheduledThreadPoolExecutor去执行,会返回一个FutureTask对象

Executor框架主要成员的介绍:

  • Executor接口:Executor框架的基础,将任务的提交和任务的执行分离。
  • ThreadPoolExecutor类:Java线程池的核心实现类,用来执行提交的任务。
  • ScheduledThreadPoolExecutor类:Executor框架的另一个关键类,可以在给定的延迟后执行命令,或者定期执行命令。
  • 还有Future接口FutureTask类Runnable接口Callable接口
③ Executor框架的使用示意图

在这里插入图片描述

  • 主线程首先创建实现Runnable或者Callable接口的任务对象
  1. 工具类Executors可以实现将Runnable对象封装成一个Callable对象,方法:Executors.callable(Runnable task)Executors.callable(Runnable task,Object result)
  • 然后可以把创建完成的Runnable对象直接交给ThreadPoolExecutor或者ScheduledThreadPoolExecutor执行,方法:ThreadPoolExecutor.execute(Runnable command)或者ScheduledThreadPoolExecutor.execute(Runnal command);或者也可以把Runnable对象或Callable对象提交给ExecutorService执行,方法:ExecutorService.submit(Runnable task)ExecutorService.submit(Callable task)
  1. 执行execute()方法和submit()方法的区别是什么呢?
    execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
    submit()方法用于提交需要返回值的任务,线程池会返回一个FutureTask类型的对象,通过这个FutureTask对象可以判断任务是否执行成功。
    ③ 对于需要要返回值的任务,可以通过FutureTask.get()方法或者FutureTask.get(long timeout, TimeUnit unit)方法来获取返回值。其中,前者会阻塞当前线程直到任务完成;后者,会阻塞当前线程一段时间后立即返回,有可能任务并没有执行完成。
  • 最后,主线程可以执行FutureTask.get()方法等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)取消任务的执行
2. Java常见的线程池
① 工厂类Executors创建线程池

FixedThreadPool

  • 使用固定线程数的线程池,适用于为了满足资源管理的需求,而限制当前线程数的应用场景。或者是适用于负载比较重的服务器
  • 创建方式:Executors.newFixedThreadPool(threadsNum)
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

SingleThreadExecutor

  • 使用单个线程的线程池,适用于需要保证各个任务顺序执行,而且在任意时刻,不会有多个活动线程的应用场景。
  • 创建方式:Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

CachedThreadPool

  • 根据需要创建新线程的线程池,适用于执行很多短期异步任务的小程序,或者是负载比较轻的服务器
  • 创建方式:Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

ScheduledThreadPool

  • 使用固定线程数的线程池,适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求,需要限制后台线程数量的应用场景。
  • 创建方式:Executors.newScheduledThreadPool(corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

SingleThreadScheduledExecutor

  • 使用单个线程的线程池,适用于需要使用单个后台线程执行周期任务,同时需要保证各个任务执行顺序的应用场景。
  • 创建方式:Executors.newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}
② FixedThreadPool
  • 使用固定线程数的线程池,具有以下特点:
  1. corePoolSize = maximumPoolSize = newFixedThreadPool(num)方法的参数num,实际只使用了corePool。
  2. keepAliveTime的值为0L,表明工作线程如果空闲会被立即终止。
  3. 使用LinkedBlockingQueue,这是一种基于链表无界队列,因为队列容量为Integer.MAX_VALUE。队列中的元素采用FIFO序
  4. 使用无界队列,FixedThreadPool在未调用shutdown()或者shutdownNow()方法的情况下,不会调用饱和策略。
  • 关于无界队列:
  1. corePoolSize满时,由于使用无界队列,新提交的任务总是可以存放在阻塞队列中。因此,线程池中的线程数永远不会超过corePoolSize
  2. 由于1,使用无界队列使得线程池中的maximumPoolSize参数无效。
  3. 由于1和2,使用无界队列使得线程池中的keepAliveTime参数无效。
  4. 由于使用无界队列,FixedThreadPool在未调用shutdown()或者shutdownNow()方法的情况下,不会调用饱和策略(即不会RejectExecutionHandler.rejectExecution()方法)。
  • FixedThreadPool的工作流程:
    在这里插入图片描述
    FixedThreadPool 的 execute() 运行示意图
  1. 如果当前运行的线程数小于corePoolSize,则创建新的线程来执行任务;
  2. 当前运行的线程数等于corePoolSize,将任务加入LinkedBlockingQueue
  3. 工作线程执行完当前的任务后,不会销毁,而是循环地从LinkedBlockingQueue中获取任务来执行;
③ SingleThreadExecutor
  • 使用单个线程的线程池,具有以下特点:
  1. corePoolSize = maximumPoolSize = 1,实际只使用了corePool。
  2. keepAliveTime的值为0L,表明工作线程如果空闲会被立即终止。
  3. 使用LinkedBlockingQueue,基于链表的无界队列,队列中的元素采用FIFO顺序。
  4. 使用无界队列,SingleThreadExecutor在未调用shutdown()或者shutdownNow()方法的情况下,不会调用饱和策略
  • SingleThreadExecutor的工作流程:
    在这里插入图片描述
    SingleThreadExecutor 的 execute() 运行示意图
  1. 如果线程池中无运行的线程,则创建一个新的线程执行任务。
  2. 如果线程池中已经有正在运行的线程,则将任务加入到LinkedBlockingQueue中。
  3. 工作线程执行完当前的任务后,不是立即销毁,而是循环地从LinkedBlockingQueue中取出任务来执行。
  • 与FixedThreadPool的区别:SingleThreadExecutor线程池中同时最多只有一个线程活跃同时最多只有一个任务在执行
④ CachedThreadPool
  • 可以根据需要创建新线程的线程池,具有以下特点:
  1. corePoolSize = 0maximumPoolSize = Integer.MAX_VALUE,表明线程池无界实际只使用了maximumPool
  2. keepAliveTime的值为60s,表明工作线程最多允许空闲60s,超过60s会被终止。
  3. 使用SynchronousQueue,是一种没有容量的阻塞队列,主线程的每个添加操作必须等待另一个线程的移除操作,反之亦然。
  4. CachedThreadPool在线程池中线程数等于maximumPoolSize时,会调用饱和策略
  • CachedThreadPool的工作流程:
    在这里插入图片描述
    CachedThreadPool 的 execute() 运行示意图
  1. 主线程首先执行Synchronous.offer(),即添加操作。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)(即移除操作),那么主线程的offer操作和空闲线程的poll操作配对成功,主线程把任务交给空闲线程执行。
  2. 如果maximumPool为空,或者maximumPool没有空闲线程,则offer操作和poll操作匹配失败。此时,CachedThreadPool会创建新的线程执行该任务。
  3. 工作线程执行完当前线程后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),这个poll操作会让空闲线程等待60秒。如果60秒内主线程发起了offer操作,这个空闲线程便会执行主线程新提交的任务;否则,这个空闲线程将会终止。
⑤ ScheduledThreadPoolExecutor
  • 有固定数量的线程池,可以在给定的延迟后执行任务,或者定期执行任务。具有以下特点:
  1. corePoolSize = Executors.newScheduledThreadPool(num)中的参数ScheduledThreadPool的参数大于等于1,而SingleThreadScheduledExecutor的参数为1maximumPoolSize = Integer.MAX_VALUE,实际只使用了corePool。
  2. keepAliveTime的值为0L,表明线程一旦空闲就会被终止。
  3. 使用DelayQueue,这是一个具有优先级无界队列,内部封装了一个PriorityQueue,可以实现任务的定期执行和延后执行。
  4. 使用无界队列,ScheduledThreadPool在未调用shutdown()或者shutdownNow()方法的情况下,不会调用饱和策略
  • ScheduledThreadPool的运行流程:
    在这里插入图片描述
    ScheduledThreadPoolExecutor 的 execute() 运行示意图
  1. 当调用ScheduledThreadPoolExecutorscheduleAtFixedRate() 方法或者scheduleWithFixedDelay() 方法时,会向DelayQueue 添加一个实现了 RunnableScheduledFuture 接口ScheduledFutureTask
  2. 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。
  • 与ThreadPoolExecutor的区别:
  1. 使用DelayQueue作为阻塞队列
  2. 获取任务的方式不同
  3. 执行周期任务后,添加了额外的处理

执行任务周期的步骤,或者如何实现任务周期执行的?

  • 预备知识:ScheduledFutureTask中的三个重要的成员变量:
  1. long time: 表示这个任务将要被执行的具体时间。当time大于等于当前系统的时间,表明该任务已到期,可以执行。
  2. long sequenceNumber: 表示这个任务被添加到ScheduledThreadPoolExecutor的序号。
  3. long period: 表示任务执行的时间间隔
  • DelayQueue中封装的PriorityQueue,会对这些任务按time进行排序,时间小的先被执行。如果time相同,则按seq进行排序,序号较小的先执行。(即如果任务的time相同,则先提交的先被执行
    在这里插入图片描述
    ScheduledThreadPoolExecutor执行任务周期的步骤
  1. 线程1使用DelayQueue.take()方法从DelayQueue中获取已经到期的ScheduledFutureTask。到期任务是指ScheduledFutureTask的time小于等于当前系统的时间。
if (first == null || first.getDelay(NANOSECONDS) > 0)
	return null;
  1. 线程1执行这个ScheduledFutureTask
  2. 线程1修改ScheduledFutureTask中的time为下次将要被执行的时间。
  3. 线程1将修改了time后的ScheduledFutureTask使用DelayQueue.add()方法,放回到DelayQueue中。
3. 关于常见线程池的问题

1. 说一下常见的四种线程池及区别

  • 基础回答: 常见的四种线程池有哪些、如何通过Executors工具类创建
  • 进阶: 每个线程池的参数设置(线程池大小、空闲线程的存活时间、使用的阻塞队列、是否调用饱和策略);每个线程池的运行流程
  • 补充讲解: 无界队列对线程池的影响

2. 线程池的底层实现?

  • 讲解Executor框架:三个组成部分接口和类之间的关系、线程池的总体运行流程、四种常见的线程池
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值