1. 概述

在现代应用开发中,多线程和并行处理是关键概念。在Java中,Executor框架提供了一种有效管理并发任务执行的方式ExecutorService接口处于这个框架的核心位置,它提供了两个常用的提交任务执行的方法:submit()execute()

本文将探讨这两个方法之间的主要区别。我们将通过一个简单的示例来展示如何使用ExecutorService,这个例子中我们模拟了一个计算数组中数字之和的任务,使用的是线程池

2. ExecutorService.submit() 的使用

首先,我们来看看submit()方法,这是ExecutorService接口中最常用的方法。它允许我们提交任务执行,并返回一个代表计算结果的Future对象

这个Future对象允许我们获取计算结果,处理执行过程中出现的异常,并监控任务状态。我们可以调用Futureget()方法来获取结果或异常

首先,初始化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()方法是一个阻塞调用,如果结果还未准备好,它会等待,可能导致线程暂停,直到结果准备就绪。如果计算过程中遇到问题,它也可能抛出InterruptedExceptionExecutionException异常

最后,关闭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上找到。