目录
ExecutorService 是Java并发编程中的一个接口,定义在 java.util.concurrent 包中。是 Java JDK 提供的框架,用于简化异步模式下任务的执行。
1.实例化 ExecutorService
实例化ExecutorService 的方式有两种:一种是工厂方法,另一种是直接创建。
1.1 工厂方法创建 ExecutorService 实例
ExecutorService
接口本身并不直接创建线程,而是通过 Executors
工具类来创建不同类型的 ExecutorService
实例。以下是一些常见的创建方法:
a.固定大小的线程池:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
这种类型的线程池包含一个固定的线程数目,当有更多任务被提交时,这些任务会在队列中等待,直到线程池中有空闲线程。
b.缓存线程池:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
缓存线程池会根据需要创建新线程,但是会在一段时间内(代码中默认空闲60s)终止那些没有任务执行的线程。
c.单线程的线程池:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
单线程的线程池保证了所有的任务都按照提交的顺序在一个线程上执行。
当然还有其它很多工厂方法,每种工厂方法都可以创建满足特定用例的预定义 ExecutorService 实例。你所需要做的就是找到自己想要的合适的方法。这些方法都在 Oracle 的 JDK 官方文档中有列出
1.2 直接创建 ExecutorService 的实例
因为ExecutorService 是只是一个接口,因此可以使用其任何实现类的实例。Java java.util.concurrent 包已经预定义了几种实现可供我们选择,或者你也可以创建自己的实现。
例如,ThreadPoolExecutor 类实现了 ExecutorService 接口并提供了一些构造函数用于配置执行程序服务及其内部池。
ExecutorService executorService =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()
);
2.ExecutorService的执行
ExecutorService
可以执行 Runnable
和 Callable
两种类型的任务。这两种任务类型有不同的使用场景和特点:
Runnable:Runnable 是一个接口,它只有一个方法 run()。当你向 ExecutorService 提交一个 Runnable 任务时,该任务将被执行,但不会返回任何结果。这是因为 Runnable 本身不支持返回值。通常,使用 Runnable 适用于那些只需要执行一些操作而不关心返回结果的情况。
Callable:Callable
也是一个接口,类似于 Runnable
,但它包含一个 call()
方法,这个方法可以返回一个结果。Callable
任务的执行结果可以通过 Future
对象来获取。因此,当你向 ExecutorService
提交一个 Callable
任务时,它不仅会被执行,而且你还可以通过 Future
获取异步计算的结果。这对于需要返回结果的任务非常有用。
两种实现如下面的代码所示:
import java.util.concurrent.*;
public class ExecutorServiceExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交一个 Runnable 任务
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("Runnable task running on thread " + Thread.currentThread().getName());
}
});
// 提交一个 Callable 任务
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("Callable task running on thread " + Thread.currentThread().getName());
// 模拟一个耗时操作
Thread.sleep(1000);
return 42; // 返回一个结果
}
});
// 获取 Callable 任务的结果
int result = future.get(); // 这里会阻塞直到任务完成
System.out.println("Result of Callable task: " + result);
// 关闭线程池
executor.shutdown();
}
}
任务的执行除了execute() 、 submit()方法,还有invokeAny() 和 invokeAll() 等方法。这些方法都继承自 Executor 接口。
2.1 execute()方法
该方法返回值为空 ( void )。因此使用该方法没有任何可能获得任务执行结果或检查任务的状态。
2.2 submit()方法
submit() 方法会将一个 Callable 或 Runnable 任务提交给 ExecutorService 并返回 Future 类型的结果。
2.3 invokeAny()方法
List<Callable<Integer>> tasks = new ArrayList<>();
tasks.add(() -> {
System.out.println("Executing task 1...");
Thread.sleep(1000);
return 42;
});
tasks.add(() -> {
System.out.println("Executing task 2...");
Thread.sleep(2000);
return 43;
});
try {
Integer result = executor.invokeAny(tasks);
System.out.println("First result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
invokeAny() 方法将一组任务分配给 ExecutorService,使每个任务执行,并返回任意一个成功执行的任务的结果 ( 如果成功执行 )。
2.4 invokeAll()方法
List<Callable<Integer>> tasks = new ArrayList<>();
tasks.add(() -> {
System.out.println("Executing task 1...");
Thread.sleep(1000);
return 42;
});
tasks.add(() -> {
System.out.println("Executing task 2...");
Thread.sleep(2000);
return 43;
});
try {
List<Future<Integer>> futures = executor.invokeAll(tasks);
} catch (InterruptedException e) {
e.printStackTrace();
}
invokeAll() 方法将一组任务分配给 ExecutorService ,使每个任务执行,并以 Future 类型的对象列表的形式返回所有任务执行的结果。
3.关闭ExecutorService
一般情况下,ExecutorService 并不会自动关闭,即使所有任务都执行完毕,或者没有要处理的任务,也不会自动销毁 ExecutorService 。它会一直出于等待状态,等待我们给它分配新的工作。所以正确关闭 ExecutorService
是非常重要的,因为如果不正确地关闭它,可能会导致资源泄露或者其他问题。以下是关闭ExecutorService的几种方式:
3.1 shutdown()方法
executorService.shutdown();
调用 shutdown()
方法后,ExecutorService
不再接受新的任务提交。然而,它将继续执行已经提交但尚未开始的任务,并等待正在执行的任务完成。(并不能确保任务正确执行并完成,异步)
3.2 shutdownNow()方法
List<Runnable> notFinishedTasks = executorService.shutdownNow();
如果需要立即停止所有任务并关闭线程池,可以调用 shutdownNow()
方法。这会尝试取消所有未完成的任务,并返回一个包含所有未完成任务的 List
。
因为提供了两个方法,因此关闭 ExecutorService 实例的最佳实战就是同时使用这两种方法并结合 awaitTermination() 方法。
3.3 awaitTermination()方法
executorService.shutdown();
try {
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("线程池没有在60秒内终止");
executorService.shutdownNow();
} else {
System.out.println("所有任务已完成,线程池已关闭");
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
使用这种方式,ExecutorService 首先停止执行新任务,等待指定的时间段完成所有任务。如果该时间到期,则立即停止执行。
调用 awaitTermination()
方法会阻塞当前线程,直到所有已提交的任务完成或超时。这一步确保了所有任务都已经完成,并且线程池可以安全地关闭。确保在所有任务完成之前,不会继续执行后续代码。