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
管理的线程池中的线程是如何并发执行任务的,每个任务只是简单地打印一条消息,然后“处理”该命令(通过睡眠模拟工作),然后再打印一条完成消息,最后,当所有任务都完成后,主线程打印出“所有线程运行完毕”。
ExecutorService
提供了一种方法将任务的提交与执行解耦开来,从而可以更加灵活地控制和管理线程的创建、执行和关闭。ExecutorService
接口中的方法大致可以分为几类:任务的提交、线程池的关闭和状态查询,下面将详细解释其中一些主要方法的含义:
void execute(Runnable command)
: 提交一个需要执行的任务,该任务没有返回值,这个方法通常用于执行不需要返回结果的操作。<T> Future<T> submit(Callable<T> task)
: 提交一个返回值的任务用于执行,并返回一个表示任务的未决结果的 Future
,Callable
对象与 Runnable
类似,但它可以返回一个结果。<T> Future<T> submit(Runnable task, T result)
: 提交一个 Runnable
任务用于执行,并返回一个表示该任务的未决结果的 Future
,该 Future
的 get
方法在任务完成后将返回给定的结果。Future<?> submit(Runnable task)
: 提交一个 Runnable
任务用于执行,并返回一个表示该任务的未决结果的 Future
,该 Future
的 get
方法在任务完成后将返回 null
。void shutdown()
: 启动线程池的关闭,这不会立即停止已经提交的任务,但会拒绝新的任务提交。List<Runnable> shutdownNow()
: 试图停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表,这个方法比较激进,通常不推荐在生产环境中使用,除非确实需要立即停止所有任务。boolean isShutdown()
: 如果线程池已关闭,则返回 true
。boolean isTerminated()
: 如果所有任务都已完成,则返回 true
。boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
: 请求关闭、发生超时或者当前线程被中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。这些方法提供了对线程池执行生命周期的细粒度控制,允许开发者以更加灵活和高效的方式管理并发任务。
ExecutorService通过Executors提供了便捷的线程池创建方式,它帮助高效地管理和复用线程,减少了资源的浪费,同时,它还能控制并发数量,保证系统的稳定性。虽然Executors简化了线程池的创建,但过度使用或配置不当可能导致系统资源耗尽或性能下降。此外,默认的异常处理策略可能不适合所有场景。
在使用Executors创建ExecutorService时,要根据实际需求选择合适的线程池类型,并合理配置线程池参数,要注意及时关闭线程池,避免资源泄露,对于异常处理,建议根据业务场景进行定制化处理。