重温JAVA线程池精髓:Executor、ExecutorService及Executors的源码剖析与应用指南

简介: 重温JAVA线程池精髓:Executor、ExecutorService及Executors的源码剖析与应用指南

1️⃣引言

在Java并发编程中,线程池是一个非常重要的概念。它可以帮助我们更好地管理和控制线程的使用,避免因为大量线程的创建和销毁带来的性能开销。Java的java.util.concurrent(简称JUC)包

中提供了一套丰富的线程池工具,包括Executor接口、ExecutorService接口以及Executors工厂类等。本文将详细介绍这些工具的使用和原理,帮助大家更好地理解和应用Java中的线程池技术。

2️⃣Executor接口

Executor接口是JUC包中定义的一个执行器接口,它只有一个execute方法,接收一个Runnable对象作为参数,并执行Runnable中的操作。这个接口非常简单,但它定义了执行器的基本功能。

public interface Executor {
    void execute(Runnable command);
}

在实际应用中,我们通常不会直接使用Executor接口,而是使用它的子接口ExecutorService,它提供了更丰富的功能。

3️⃣ExecutorService接口

ExecutorService接口继承自Executor接口,并增加了关于执行器服务的定义。它提供了一系列的方法,包括关闭执行器、立即关闭、检查执行器是否关闭、等待任务终止、提交有返回值的任务以及批量提交任务等。

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <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;
}

这些方法的具体含义和使用方式如下:

  • shutdown():关闭执行器,已提交的任务会继续执行,但不接受新的任务。
  • shutdownNow():立即关闭执行器,尝试停止所有正在执行的任务,并返回等待执行的任务列表。
  • isShutdown():检查执行器是否已关闭。
  • isTerminated():检查执行器是否已终止,即所有任务都已完成。
  • awaitTermination(long timeout, TimeUnit unit):等待任务终止,如果超过指定时间则返回false。
  • submit(Callable<T> task):提交一个有返回值的任务,并返回一个Future对象,通过该对象可以查看任务执行是否完成,并获取返回值。
  • submit(Runnable task, T result):提交一个Runnable任务和一个结果值,当任务执行完成后,返回该结果值。注意这个结果值是在任务执行前就确定的,与任务的实际执行结果无关。如果希望获取任务的实际执行结果,应该使用Callable任务。
  • submit(Runnable task):提交一个Runnable任务,并返回一个Future对象。由于Runnable任务没有返回值,所以这个Future对象的get方法将返回null。这个方法主要用于将Runnable任务转换为Future对象,以便使用Future的相关功能(如取消任务、检查任务是否完成等)。但这个用法并不常见,因为Runnable任务本身就不支持返回值。更常见的做法是直接使用execute(Runnable command)方法执行Runnable任务。
  • invokeAll(Collection<? extends Callable<T>> tasks):批量提交Callable任务,并返回一个Future对象的列表。当所有任务都完成后,可以通过这些Future对象获取任务的返回值。如果某个任务执行失败,那么对应的Future对象的get方法将抛出ExecutionException异常。这个方法会等待所有任务都完成后才返回。如果希望设置超时时间,可以使用另一个重载版本的方法。
  • invokeAny(Collection<? extends Callable<T>> tasks):批量提交Callable任务,并返回第一个成功完成的任务的返回值。当找到第一个成功完成的任务后,该方法会立即返回,而不会等待其他任务完成。如果所有任务都失败,那么该方法将抛出ExecutionException异常。这个方法通常用于实现“多个路径中选择一个最快路径”的场景。同样地,这个方法也有一个设置超时时间的重载版本。

需要注意的是,虽然ExecutorService接口提供了很多功能强大的方法,但我们在实际使用中并不需要记住所有这些方法。大部分情况下,我们只需要关注几个常用的方法就足够了,比如execute()、submit()和shutdown()等。其他的方法可以在需要时查阅文档或参考资料。

4️⃣Executors工厂类

Executors是一个工厂类,它提供了一系列静态方法来创建不同类型的线程池。这些线程池都是ExecutorService接口的实现类。通过Executors的工厂方法,我们可以非常方便地创建和管理线程池。下面介绍几种常见的线程池类型:

3.1. FixedThreadPool

  • 固定大小的线程池。创建时指定线程池的大小,当有新任务提交时,如果线程池中有空闲线程,则使用空闲线程执行任务;
  • 如果没有空闲线程,则新任务会等待直到有线程空闲出来。这种线程池适用于已知并发压力的情况下,对线程数做限制,避免由于大量线程的创建和销毁带来的性能开销。
  • 使用:ExecutorService executor = Executors.newFixedThreadPool(10); 创建一个大小为10的固定线程池。

3.2. WorkStealingPool

  • 拥有多个任务队列的线程池(在ForkJoinPool中实现)。这种线程池可以减少线程间的竞争和上下文切换开销,提高处理器的利用率。
  • 它适用于大量异步任务的场景,如并行计算、大数据处理等。

3.3. SingleThreadExecutor

  • 单线程执行器。顾名思义,这种线程池中只有一个线程执行任务。
  • 所有提交的任务都会按照提交的顺序依次执行。
  • 这种线程池适用于需要保证任务执行顺序的场景,如日志记录、事件驱动等。
  • 使用:ExecutorService executor = Executors.newSingleThreadExecutor(); 创建一个单线程执行器。

3.4. CachedThreadPool

  • 可缓存的线程池,这种线程池会根据需要创建线程来执行任务,并且可以重复利用已存在的线程来执行新的任务。
  • 当线程池中的线程在一定时间内没有执行任务时,它会被自动销毁以释放资源。
  • 这种线程池适用于并发压力较大且任务执行时间较短的场景,如Web服务器处理HTTP请求等。
  • 使用:ExecutorService executor = Executors.newCachedThreadPool(); 创建一个可缓存的线程池。
  • 但需要注意的是,在实际应用中我们可能需要更加谨慎地使用CachedThreadPool,因为如果不当使用可能会导致系统资源耗尽(如创建过- 多的线程导致内存溢出等)。因此在使用CachedThreadPool时需要特别关注任务的执行时间和数量以及系统的资源状况等因素。

3.5. SingleThreadScheduledExecutor和 ScheduledThreadPool

  • 这两种线程池都支持定时或周期性执行任务。
  • SingleThreadScheduledExecutor是一个单线程的定时任务执行器,它按照任务提交的顺序依次执行定时任务或周期性任务;
  • 而ScheduledThreadPool是一个可以指定线程数量的定时任务执行器,它可以同时执行多个定时任务或周期性任务。
  • 两种线程池适用于需要定时触发或周期性触发的场景,如定时发送邮件、定时更新缓存等。
  • 使用:ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); 或 ScheduledExecutorService executor = Executors.newScheduledThreadPool(10); 分别创建一个单线程定时任务执行器和一个大小为10的定时任务执行器。

3.6 注意事项

需要注意的是,虽然Executors工厂类提供了很多方便的静态方法来创建线程池,但在实际使用中我们也需要关注线程池的配置和管理问题。

  • 例如我们需要根据实际的应用场景和需求来选择合适的线程池类型和大小;
  • 我们需要在不再需要线程池时及时关闭它以释放资源;
  • 我们还需要关注线程池中的任务执行情况和异常处理等。

只有合理地配置和管理线程池,我们才能充分发挥它的优势并提高系统的性能和稳定性。

5️⃣探讨一个问题:线程池的优雅关闭

线程池的优雅关闭指的是在不再需要线程池时,能够平滑地终止其执行,释放相关资源,并确保正在执行的任务能够完成或得到妥善处理。我们使用ExecutorService接口提供的关闭方法可以实现线程池的优雅关闭。

下面是实现线程池优雅关闭的一般步骤:

  1. 调用shutdown()方法:首先调用ExecutorServiceshutdown()方法,它将启动线程池的关闭过程。此时,线程池不再接受新任务的提交,但会继续处理队列中等待的任务。
  2. 等待任务完成:接着,可以使用awaitTermination方法来等待线程池中所有任务都执行完毕。这个方法接受两个参数:超时时间和时间单位。如果在指定的超时时间内所有任务都执行完毕,则方法返回true;否则返回false。可以根据需要设置合适的超时时间。
  3. 处理未完成任务(可选):如果在等待超时后仍有任务未执行完毕,可以选择调用shutdownNow()方法来尝试立即停止所有正在执行的任务,并返回队列中等待执行的任务列表。然后,可以对这些未完成的任务进行补救操作,如记录日志、重新提交到另一个线程池等。但请注意,shutdownNow()方法并不保证能立即停止所有任务,因为线程的执行是由操作系统调度的。
  4. 检查线程池状态:最后,可以检查线程池的状态来确保它已经完全关闭。可以使用isTerminated()方法来检查线程池是否已关闭且所有任务都已完成。如果返回true,则表示线程池已成功关闭;否则,可能需要进一步处理未完成的任务或检查线程池的配置。

代码如下:

ExecutorService executorService = Executors.newFixedThreadPool(10);
// 提交任务到线程池...

// 启动关闭过程
executorService.shutdown();
try {
    // 等待任务完成,这里设置超时时间为60秒
    if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
        // 超时后仍有任务未执行完毕,可以选择强制关闭
        List<Runnable> pendingTasks = executorService.shutdownNow();
        // 处理未完成的任务...
    }
} catch (InterruptedException e) {
    // 处理中断异常...
    executorService.shutdownNow(); // 保留中断状态
    // 再次检查线程池状态和处理未完成的任务...
} finally {
    if (!executorService.isTerminated()) {
        // 线程池未正常关闭,记录日志或进行其他处理...
    }
}

通过上述步骤,可以实现线程池的优雅关闭,确保资源的正确释放和任务的妥善处理。

6️⃣结语

总之,Executor、ExecutorService接口和Executors工厂类共同构成了Java中强大而灵活的线程池框架。

  • Executor接口定义了执行任务的基本行为,它是线程池框架的基石。
  • 而ExecutorService接口则扩展了Executor的功能,提供了一系列丰富的方法来管理和控制任务的执行。
  • 最后,Executors工厂类为开发者提供了创建各种类型线程池的便捷途径,无需手动实现复杂的线程池逻辑。

它们允许以简单而高效的方式管理和控制并发任务的执行,提高了系统的性能和可伸缩性。在使用线程池时,建议根据具体的应用场景和需求选择合适的线程池类型,并注意正确地管理线程池的生命周期和任务提交。


相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
6天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
15天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
7天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
24 3
|
8天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
20 3
|
12天前
|
运维 自然语言处理 供应链
Java云HIS医院管理系统源码 病案管理、医保业务、门诊、住院、电子病历编辑器
通过门诊的申请,或者直接住院登记,通过”护士工作站“分配患者,完成后,进入医生患者列表,医生对应开具”长期医嘱“和”临时医嘱“,并在电子病历中,记录病情。病人出院时,停止长期医嘱,开具出院医嘱。进入出院审核,审核医嘱与住院通过后,病人结清缴费,完成出院。
41 3
|
18天前
|
SQL 监控 Java
技术前沿:Java连接池技术的最新发展与应用
本文探讨了Java连接池技术的最新发展与应用,包括高性能与低延迟、智能化管理和监控、扩展性与兼容性等方面。同时,结合最佳实践,介绍了如何选择合适的连接池库、合理配置参数、使用监控工具及优化数据库操作,为开发者提供了一份详尽的技术指南。
28 7
|
16天前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
30 3
|
16天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
33 2
|
17天前
|
缓存 Java 数据库连接
Hibernate:Java持久层框架的高效应用
通过上述步骤,可以在Java项目中高效应用Hibernate框架,实现对关系数据库的透明持久化管理。Hibernate提供的强大功能和灵活配置,使得开发者能够专注于业务逻辑的实现,而不必过多关注底层数据库操作。
11 1