1. 概述
在现代应用开发中,多线程和并行处理是关键概念。在Java中,Executor
框架提供了一种有效管理并发任务执行的方式。ExecutorService
接口处于这个框架的核心位置,它提供了两个常用的提交任务执行的方法:submit()
和 execute()
。
本文将探讨这两个方法之间的主要区别。我们将通过一个简单的示例来展示如何使用ExecutorService
,这个例子中我们模拟了一个计算数组中数字之和的任务,使用的是线程池。
2. ExecutorService.submit()
的使用
首先,我们来看看submit()
方法,这是ExecutorService
接口中最常用的方法。它允许我们提交任务执行,并返回一个代表计算结果的Future
对象。
这个Future
对象允许我们获取计算结果,处理执行过程中出现的异常,并监控任务状态。我们可以调用Future
的get()
方法来获取结果或异常。
首先,初始化ExecutorService
:
ExecutorService executorService = Executors.newFixedThreadPool(2);
这里,我们使用固定大小的线程池(大小为2)初始化ExecutorService
,它创建了一个利用共享无界队列操作的固定数量线程。在我们的场景中,任何时候最多只有两个线程会活跃地处理任务。如果在所有现有任务都在执行时发送更多任务,它们会被暂存在队列中,直到有处理线程空闲。
接下来,我们使用Callable
创建一个任务:
Callable<Integer> task = () -> {
int[] numbers = {1, 2, 3, 4, 5};
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
};
重要的是,这里的Callable
对象表示一个可以返回结果且可能抛出异常的任务。在这个例子中,它代表了另一个线程可以执行的任务,并返回结果或可能抛出异常。这个Callable
计算数组中整数的和并返回结果。
现在,既然我们已经将任务定义为Callable
,让我们将其提交给ExecutorService
:
Future<Integer> result = executorService.submit(task);
简单来说,submit()
方法接受Callable
任务并将其提交给ExecutorService
执行。它返回一个Future<Integer>
对象,代表计算的未来结果。总的来说,executorService.submit()
是一种使用ExecutorService
异步执行Callable
任务的方式,允许并发执行任务并通过返回的Future
获取结果。
最后,检查结果:
try {
int sum = result.get();
System.out.println("Sum calculated using submit:" + sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
这里,get()
方法获取任务执行的结果。在这个例子中,它获取任务计算出的和并打印出来。但是,需要注意的是,get()
方法是一个阻塞调用,如果结果还未准备好,它会等待,可能导致线程暂停,直到结果准备就绪。如果计算过程中遇到问题,它也可能抛出InterruptedException
或ExecutionException
异常。
最后,关闭ExecutorService
:
executorService.shutdown();
在任务完成执行后,这将关闭ExecutorService
并释放服务使用的任何资源。
3. ExecutorService.execute()
的使用
execute()
方法是Executor
接口中定义的一个更简单的方法,它是ExecutorService
的父接口。它用于提交任务执行,但不返回Future
。这意味着我们不能直接通过Future
对象获取任务结果或处理异常。
它适用于不需要等待任务结果、也不期望有任何异常的情况。这些任务是为了产生副作用而执行的。
与之前一样,我们使用固定大小的线程池(大小为2)创建ExecutorService
。然而,我们将任务设置为Runnable
:
Runnable task = () -> {
int[] numbers = {1, 2, 3, 4, 5};
int sum = 0;
for (int num : numbers) {
sum += num;
}
System.out.println("Sum calculated using execute: " + sum);
};
重要的是,任务不返回任何结果,它只是计算和打印结果。现在,我们将Runnable
任务提交给ExecutorService
:
executorService.execute(task);
这仍然是一个异步操作,表明线程池中的一个线程将执行任务。
4. 总结
在这篇文章中,我们探讨了submit()
和execute()
方法在ExecutorService
接口中的关键特性和用法。简而言之,这两种方法都提供了提交任务并发执行的方式,但它们在处理任务结果和异常的方式上有所不同。
选择submit()
还是execute()
取决于具体需求。如果我们需要获取任务结果或处理异常,应该使用submit()
。另一方面,如果我们有一个不返回结果的任务,并希望“开弓没有回头箭”,那么execute()
是正确的选择。
此外,对于更复杂的使用场景,如果需要灵活管理任务并获取结果或异常,submit()
是更好的选择。
如往常一样,本文的完整代码可以在GitHub上找到。