一文搞懂Executor执行器和线程池的关系,整体介绍其任务执行/调度体系:ThreadPoolExecutor、ScheduledExecutorService(上)

简介: 一文搞懂Executor执行器和线程池的关系,整体介绍其任务执行/调度体系:ThreadPoolExecutor、ScheduledExecutorService(上)

前言


本文进行JavaSE基础内容:Executor执行器体系的整体介绍。该文是整体框架介绍,并非局限于某一个使用的细节。由于我不止一次的被咨询说ExecutorService和ScheduledExecutorService什么区别和联系,以及ThreadPoolExecutor和ThreadPoolTaskExecutor有什么不一样之类的问题,因此决定写此文科普一下。


本文内容不深,但比较全,因此我相信绝对是你值得阅读的一篇文章(即使你可能已经工作了5年+)。


说明:流行框架如Spring、MyBatis、Netflix等一般均对Executor体系有所扩展,本文只讲述JDK的内容,其它的举一反三即可。


正文


java.util.concurrent.Executor它属于J.U.C体系里的重要一员,是在JDK 1.5之后新增的内容,由大名鼎鼎的Doug Lea大神操刀。因为它的出现才使得我们大量的使用多线程开发程序成为了可能。


Executor执行器体系整体的框架可描述为如图所示(只例举出重要的API,覆盖绝大部分场景):


image.png


Executor 执行器

执行器,可执行任意一个Runnable任务。该接口提供了一种将任务提交与如何运行每个任务的机制(包括线程使用、调度等细节)分离的方法。因此任务它自己并不需要关心线程的创建、调度细节。


画外音:可能直接执行,也可能把你交给线程池执行,总之我帮你执行就欧克了


public interface Executor {
  // @since 1.5
  void execute(Runnable command);
}


需要注意的是:该执行器并不规定是同步执行还是异步执行你提交上来的任务,下面分别举例。

画外音:你之前一直以为Executor一定是异步,那是错的。应该说:绝大部分情况下我们会去异步执行,且绝大部分情况下均会使用线程池。但绝大多数并不代表所有


同步执行任务


@Test
public void fun1() {
    String mainThreadName = Thread.currentThread().getName();
    System.out.println("----------主线程[" + mainThreadName + "]开始----------");
    // 自己定义一个同步执行器
    Executor syncExecutor = (Runnable command) -> command.run();
    // 提交任务
    syncExecutor.execute(() -> {
        String currThreadName = Thread.currentThread().getName();
        System.out.println("线程[" + currThreadName + "] 我是同步执行的...");
    });
}


运行程序,控制台打印:

----------主线程[main]开始----------
线程[main] 我是同步执行的...


特点:自己直接显示调用Runnable#run那便是简单的方法调用而已,属于同步


异步执行任务

@Test
public void fun2() throws InterruptedException {
    String mainThreadName = Thread.currentThread().getName();
    System.out.println("----------主线程[" + mainThreadName + "]开始----------");
    // 自己定义一个异步执行器
    Executor asyncExecutor = (Runnable command) -> new Thread(command).start();
    // 提交任务
    asyncExecutor.execute(() -> {
        String currThreadName = Thread.currentThread().getName();
        System.out.println("线程[" + currThreadName + "] 我是异步执行的...");
    });
    TimeUnit.SECONDS.sleep(1);
}


运行程序,控制台打印:

----------主线程[main]开始----------
线程[Thread-0] 我是异步执行的...


特点:new了一个Thread去执行command任务,调度交由系统去掌控,属于异步。


通过如上两个示例可以看到Executor它并不代表线程池(线程池的英文是:ThreadPool好麽~),仅是任务执行器而已。抽象出这么一个接口是想屏蔽掉内部执行、调度的细节,方面使用者的使用而已。


ExecutorService


Executor过于抽象,仅代表着任务的执行(甚至同步、异步都没规定),因此直接通过实现该顶层接口的类并没有,平时使用得最多还是这个子接口ExecutorService:提供更多的服务。


// @since 1.5
public interface ExecutorService extends Executor {
  // 关闭。执行已经提交的任务,但不接受新任务。
  // 此方法用于关闭不需要使用的执行器,内部会做资源回收的操作,如回收线程池
  void shutdown();
  // 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表
  // 此方法一般不使用
  List<Runnable> shutdownNow();
  // 执行器是否已经被关闭
  boolean isShutdown();
  // 只有当shutdown()或者shutdownNow()被调用,而且所有任务都执行完成后才会返回true
  boolean isTerminated();
  // 阻塞。直到所有的任务都被执行完,或者超时
  boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
  // =====下面为任务提交方法(使用Future跟踪任务执行情况)=====
  // 以下三个是最最最最最最最常用的方法:执行任务
  // Callable任务有返回值。所以Future.get()的时候可以拿到此返回值
  <T> Future<T> submit(Callable<T> task);
  // Runnable任务。执行完后Future.get()永远返回的是result这个值
  <T> Future<T> submit(Runnable task, T result);
  // 执行完后Future.get()永远返回的是null
  Future<?> submit(Runnable task);
  // 这几个方法在**批量执行**或**多选一**的业务场景中非常方便。
  // invokeAll:所有任务都完成(包括成功/被中断/超时)后才会返回,所以它会阻塞哦(时间由最长的决定)
  // invokeAny()在任意一个任务成功(被中断/超时)后就会返回,只需要成功一个就返回(时间由最短的决定)
  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
  <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
  <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}


其实提交的Runnable任务最终都会通过Executors.callable(runnable, result)适配为一个Callable<V>去执行的,具体源码处有兴趣的可以看一看。


从类图中可以看出,ExecutorService的实现分为两个分支:左边的AbstractExecutorService(一般为基于线程池的实现),以及右边的ScheduledExecutorService延迟/周期执行服务,下面也得分别作出解释。


画外音:submit/invokeAll等方法如何去执行任务,调用execute()放入线程池or周期性执行,或者结合在一起?这是它的两大方向



相关文章
|
6月前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
275 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
10月前
|
存储 Java 数据库
如何处理线程池关闭时未完成的任务?
总之,处理线程池关闭时未完成的任务需要综合考虑多种因素,并根据实际情况选择合适的处理方式。通过合理的处理,可以最大程度地减少任务丢失和数据不一致等问题,确保系统的稳定运行和业务的顺利开展。
443 64
|
10月前
|
消息中间件 监控 Java
线程池关闭时未完成的任务如何保证数据的一致性?
保证线程池关闭时未完成任务的数据一致性需要综合运用多种方法和机制。通过备份与恢复、事务管理、任务状态记录与恢复、数据同步与协调、错误处理与补偿、监控与预警等手段的结合,以及结合具体业务场景进行分析和制定策略,能够最大程度地确保数据的一致性,保障系统的稳定运行和业务的顺利开展。同时,不断地优化和改进这些方法和机制,也是提高系统性能和可靠性的重要途径。
274 62
|
8月前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
258 16
|
8月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
322 17
|
7月前
|
数据采集 Java 数据处理
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
317 0
|
10月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
195 12
|
10月前
|
开发框架 Java .NET
.net core 非阻塞的异步编程 及 线程调度过程
【11月更文挑战第12天】本文介绍了.NET Core中的非阻塞异步编程,包括其基本概念、实现方式及应用示例。通过`async`和`await`关键字,程序可在等待I/O操作时保持线程不被阻塞,提高性能。文章还详细说明了异步方法的基础示例、线程调度过程、延续任务机制、同步上下文的作用以及如何使用`Task.WhenAll`和`Task.WhenAny`处理多个异步任务的并发执行。
183 1
|
11月前
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
252 5
|
存储 Java 数据处理
进程中的线程调度
进程是应用程序运行的基本单位,包括主线程、用户线程和守护线程。计算机由存储器和处理器协同操作,操作系统设计为分时和分任务模式。在个人PC普及后,基于用户的时间片异步任务操作系统确保了更好的体验和性能。线程作为进程的调度单元,通过覆写`Thread`类的`run`方法来处理任务数据,并由系统调度框架统一管理。微服务架构进一步将应用分解为多个子服务,在不同节点上执行,提高数据处理效率与容错性,特别是在大规模数据存储和处理中表现显著。例如,利用微服务框架可以优化算法,加速业务逻辑处理,并在不同区块间分配海量数据存储任务。