线程管理神器:ExecutorService全面解析

发布时间:2024年01月23日

线程管理神器:ExecutorService全面解析 - 程序员古德

内容摘要

ExecutorService是Java中强大的线程管理工具,其优点显著:它简化了线程的生命周期管理,有效避免了大量线程的频繁创建与销毁,从而降低了系统开销;同时,通过线程池复用线程,提高了资源利用率和程序响应速度;此外,它还提供了丰富的任务调度和并发控制能力,使得多线程编程更加灵活、高效。

官方文档:https://docx.iamqiang.com/jdk11/api/java.base/java/util/concurrent/ExecutorService.html

核心概念

ExecutorService主要用来管理和控制线程,是Java并发编程的重要工具,在实际业务中,ExecutorService的使用场景非常广泛,比如,经常需要处理大量的用户请求,而每个请求都需要一个独立的线程去处理,如果直接为每个请求创建一个新的线程,会消耗大量的系统资源,而且管理起来也非常麻烦,此时,就可以使用ExecutorService来解决问题。

比如,可以创建一个固定大小的线程池,然后使用ExecutorService来管理这些线程,当有新的用户请求到来时,可以将这个请求提交给ExecutorService来处理,ExecutorService会自动选择一个空闲的线程来处理这个请求,避免了为每个请求创建新的线程的开销。

形象地说,ExecutorService就像是一家餐厅的大厨,负责管理和调度餐厅里的厨师,当有新的订单(用户请求)到来时,大厨会找到一个空闲的厨师(线程)来处理这个订单,避免了为每个订单(用户请求)重新招聘厨师(创建线程)的麻烦,这样一来,餐厅就能更高效地处理订单(用户请求),而且还能节约大量的资源。

代码案例

以下是一个使用ExecutorService的简单的Java代码示例,如下所示:

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.TimeUnit;  
  
public class ExecutorServiceExample {  
  
    public static void main(String[] args) {  
        // 创建一个固定大小的线程池  
        ExecutorService executor = Executors.newFixedThreadPool(5);  
  
        // 提交10个任务到线程池  
        for (int i = 0; i < 10; i++) {  
            Runnable worker = new WorkerThread("" + i);  
            executor.execute(worker); // 调用execute方法启动线程  
        }  
  
        // 关闭线程池(这将会导致之前提交的任务被终止,所以通常会在所有任务都提交之后再关闭)  
        // 在这个例子中,并不立即关闭它,而是在所有任务完成后才关闭  
        // executor.shutdown();  
  
        // 为了展示,让主线程睡眠一段时间,以确保所有任务都有时间完成  
        try {  
            TimeUnit.SECONDS.sleep(3);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
        // 关闭线程池(确保所有任务都已经完成)  
        executor.shutdown();  
  
        // 等待所有任务完成  
        try {  
            if (!executor.awaitTermination(800, TimeUnit.MILLISECONDS)) {  
                // 如果等待时间到了,但线程池还没有关闭,那么可以选择强制关闭  
                executor.shutdownNow();  
            }  
        } catch (InterruptedException e) {  
            executor.shutdownNow();  
        }  
  
        System.out.println("所有线程运行完毕");  
    }  
  
    /**  
     * 内部类,实现Runnable接口,作为线程的任务  
     */  
    public static class WorkerThread implements Runnable {  
  
        private String command;  
  
        public WorkerThread(String command) {  
            this.command = command;  
        }  
  
        @Override  
        public void run() {  
            System.out.println(Thread.currentThread().getName() + " 开始处理命令: " + command);  
            processCommand();  
            System.out.println(Thread.currentThread().getName() + " 结束处理命令");  
        }  
  
        private void processCommand() {  
            try {  
                Thread.sleep(500);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
  
        @Override  
        public String toString() {  
            return this.command;  
        }  
    }  
}

上面代码中,为了简化代码,使用了TimeUnit.SECONDS.sleep(3);来模拟所有任务完成的时间,运行代码,如下输出结果:

pool-1-thread-1 开始处理命令: 0  
pool-1-thread-2 开始处理命令: 1  
pool-1-thread-3 开始处理命令: 2  
pool-1-thread-4 开始处理命令: 3  
pool-1-thread-5 开始处理命令: 4  
pool-1-thread-2 结束处理命令  
pool-1-thread-3 结束处理命令  
pool-1-thread-1 结束处理命令  
pool-1-thread-5 结束处理命令  
pool-1-thread-4 结束处理命令  
pool-1-thread-1 开始处理命令: 5  
pool-1-thread-2 开始处理命令: 6  
pool-1-thread-3 开始处理命令: 7  
pool-1-thread-4 开始处理命令: 8  
pool-1-thread-5 开始处理命令: 9  
pool-1-thread-3 结束处理命令  
pool-1-thread-4 结束处理命令  
pool-1-thread-1 结束处理命令  
pool-1-thread-2 结束处理命令  
pool-1-thread-5 结束处理命令  
所有线程运行完毕

输出显示了由ExecutorService管理的线程池中的线程是如何并发执行任务的,每个任务只是简单地打印一条消息,然后“处理”该命令(通过睡眠模拟工作),然后再打印一条完成消息,最后,当所有任务都完成后,主线程打印出“所有线程运行完毕”。

核心API

ExecutorService 提供了一种方法将任务的提交与执行解耦开来,从而可以更加灵活地控制和管理线程的创建、执行和关闭。ExecutorService 接口中的方法大致可以分为几类:任务的提交、线程池的关闭和状态查询,下面将详细解释其中一些主要方法的含义:

  1. 任务的提交
    • void execute(Runnable command): 提交一个需要执行的任务,该任务没有返回值,这个方法通常用于执行不需要返回结果的操作。
    • <T> Future<T> submit(Callable<T> task): 提交一个返回值的任务用于执行,并返回一个表示任务的未决结果的 FutureCallable 对象与 Runnable 类似,但它可以返回一个结果。
    • <T> Future<T> submit(Runnable task, T result): 提交一个 Runnable 任务用于执行,并返回一个表示该任务的未决结果的 Future,该 Futureget 方法在任务完成后将返回给定的结果。
    • Future<?> submit(Runnable task): 提交一个 Runnable 任务用于执行,并返回一个表示该任务的未决结果的 Future,该 Futureget 方法在任务完成后将返回 null
  2. 线程池的关闭
    • void shutdown(): 启动线程池的关闭,这不会立即停止已经提交的任务,但会拒绝新的任务提交。
    • List<Runnable> shutdownNow(): 试图停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表,这个方法比较激进,通常不推荐在生产环境中使用,除非确实需要立即停止所有任务。
  3. 状态查询
    • boolean isShutdown(): 如果线程池已关闭,则返回 true
    • boolean isTerminated(): 如果所有任务都已完成,则返回 true
    • boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException: 请求关闭、发生超时或者当前线程被中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。

这些方法提供了对线程池执行生命周期的细粒度控制,允许开发者以更加灵活和高效的方式管理并发任务。

核心总结

线程管理神器:ExecutorService全面解析 - 程序员古德

ExecutorService通过Executors提供了便捷的线程池创建方式,它帮助高效地管理和复用线程,减少了资源的浪费,同时,它还能控制并发数量,保证系统的稳定性。虽然Executors简化了线程池的创建,但过度使用或配置不当可能导致系统资源耗尽或性能下降。此外,默认的异常处理策略可能不适合所有场景。

在使用Executors创建ExecutorService时,要根据实际需求选择合适的线程池类型,并合理配置线程池参数,要注意及时关闭线程池,避免资源泄露,对于异常处理,建议根据业务场景进行定制化处理。

关注我,每天学习互联网编程技术 - 程序员古德

文章来源:https://blog.csdn.net/qusikao/article/details/135761026
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。