Future和 FutureTask
Future
和 FutureTask
在Java并发编程中扮演着重要的角色,它们主要用于异步计算的结果。尽管它们都与异步任务的执行和结果的处理有关,但它们之间还是有一些区别的。
Future
Future
是一个接口,它提供了检查计算是否完成、等待计算完成以及检索计算结果的方法。它通常与 ExecutorService
一起使用,以异步方式执行任务。Future
允许你启动一个可能耗时的计算,而不必等待它完成就能继续执行其他任务。然后,你可以使用 Future
对象来查询计算是否完成,等待计算完成,并检索计算的结果。
主要的方法包括:
boolean isDone()
:如果任务完成,则返回true
。boolean isCancelled()
:如果任务被取消,则返回true
。V get()
:等待任务完成,然后获取其结果。V get(long timeout, TimeUnit unit)
:等待任务在给定的时间范围内完成,并获取其结果。boolean cancel(boolean mayInterruptIfRunning)
:尝试取消任务的执行。
FutureTask
FutureTask
是 Future
接口的一个实现类,它实现了 Runnable
和 Future
接口。它可以将一个 Callable
(可以返回结果的 Runnable
)或 Runnable
任务包装成一个 Future
任务。FutureTask
主要用于异步计算,并且它可以让你将计算的结果存储在一个 Future
对象中,以便后续可以查询和获取这个结果。
FutureTask
提供了对启动和取消异步计算的支持,以及查询计算是否完成和检索计算结果的方法。此外,FutureTask
还可以作为一个 Runnable
对象被提交给 Executor
执行,这意呀着你可以将 FutureTask
提交给 ExecutorService
来异步执行。
主要区别
- 实现与接口:
Future
是一个接口,而FutureTask
是Future
接口的一个具体实现。 - 功能:
Future
主要定义了异步计算结果的获取方法,而FutureTask
除了这些功能外,还实现了Runnable
接口,因此可以直接提交给Executor
执行。 - 使用场景:当你只需要一个
Future
功能的引用时(比如,你已经有了一个Future
类型的变量,但你不想或不需要知道具体的实现),你会使用Future
。而当你需要提交一个可返回结果的异步任务给Executor
执行时,你会使用FutureTask
。
示例
使用 FutureTask
提交任务给 ExecutorService
的示例:
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
// 模拟耗时任务
TimeUnit.SECONDS.sleep(1);
return 123;
};
FutureTask<Integer> futureTask = new FutureTask<>(task);
executor.submit(futureTask);
// ... 可以执行其他任务
try {
// 等待任务完成并获取结果
Integer result = futureTask.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
在这个示例中,我们创建了一个 Callable
任务,并将其包装在 FutureTask
中,然后提交给 ExecutorService
异步执行。最后,我们使用 futureTask.get()
方法等待任务完成并获取结果。
与 ExecutorService 一起使用的示例代码
确实,FutureTask
通常与 ExecutorService
一起使用来实现异步计算。以下是一个使用 FutureTask
和 ExecutorService
的示例代码,该代码展示了如何提交一个异步任务,并在需要时获取其结果。
import java.util.concurrent.*;
public class FutureTaskExample {
public static void main(String[] args) {
// 创建一个ExecutorService,这里使用固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 创建一个Callable任务,它将在未来某个时间点由ExecutorService执行
Callable<String> task = () -> {
// 模拟耗时操作
TimeUnit.SECONDS.sleep(2);
return "Hello from FutureTask";
};
// 将Callable任务包装在FutureTask中
// 注意:这里我们实际上可以直接将Callable提交给executor.submit(),它会自动返回一个Future<?>,但为了演示FutureTask,我们显式地创建它
FutureTask<String> futureTask = new FutureTask<>(task);
// 提交FutureTask给ExecutorService执行
// 注意:由于FutureTask实现了Runnable接口,我们可以直接提交它
executor.submit(futureTask);
// 在这里,你可以执行其他任务,而不需要等待上面的任务完成
// ... 其他代码 ...
// 当你需要上面任务的结果时,可以调用futureTask.get()来等待结果
try {
// get()方法会阻塞当前线程,直到任务完成并返回结果
String result = futureTask.get();
System.out.println("Result from FutureTask: " + result);
} catch (InterruptedException | ExecutionException e) {
// 处理异常
e.printStackTrace();
}
// 关闭ExecutorService,不再接受新任务,并且等待所有已提交的任务完成
executor.shutdown();
// 如果你想立即停止所有正在执行的任务,并尝试停止等待执行的任务,可以使用shutdownNow()
// 但请注意,shutdownNow()并不保证能停止所有正在执行的任务
// executor.shutdownNow();
}
}
在这个示例中,我们创建了一个 ExecutorService
,它是一个固定大小的线程池。然后,我们定义了一个 Callable
任务,该任务在执行时会模拟一个耗时操作(例如,通过 TimeUnit.SECONDS.sleep(2);
暂停2秒)。我们将这个 Callable
任务包装在 FutureTask
中,并将其提交给 ExecutorService
执行。
由于 FutureTask
实现了 Runnable
接口,我们可以直接将它提交给 ExecutorService
。然而,提交 Callable
给 executor.submit(Callable<T> task)
方法也会返回一个 Future<T>
,这实际上是 FutureTask
的一个实例(或者至少其行为与 FutureTask
类似)。但在这个示例中,我们为了展示 FutureTask
的用法而显式地创建了它。
最后,我们通过调用 futureTask.get()
方法来等待任务完成并获取其结果。如果任务尚未完成,get()
方法将阻塞调用它的线程,直到任务完成。一旦任务完成,get()
方法将返回任务的结果。
请注意,在实际应用程序中,你应该在不再需要 ExecutorService
时调用 shutdown()
方法来关闭它,以释放其资源。如果你需要立即停止所有正在执行的任务(尽管这通常不是一个好主意,因为它可能会导致数据不一致等问题),你可以调用 shutdownNow()
方法。但是,请注意 shutdownNow()
方法并不保证能够停止所有正在执行的任务。