54

既然 use ExecutorServicecan submita Callabletask and return a Future,为什么需要 use FutureTaskto wrap Callabletask and use the methodexecute呢?我觉得他们都做同样的事情。

4

6 回答 6

47

FutureTask 这个类提供了一个base implementation of Future, 方法来启动和取消一个计算

未来是接口

于 2011-02-10T12:10:43.793 回答
26

事实上你是对的。这两种方法是相同的。您通常不需要自己包装它们。如果你是,你很可能复制了 AbstractExecutorService 中的代码:

/**
 * Returns a <tt>RunnableFuture</tt> for the given callable task.
 *
 * @param callable the callable task being wrapped
 * @return a <tt>RunnableFuture</tt> which when run will call the
 * underlying callable and which, as a <tt>Future</tt>, will yield
 * the callable's result as its result and provide for
 * cancellation of the underlying task.
 * @since 1.6
 */
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

Future 和 RunnableFuture 之间的唯一区别是 run() 方法:

/**
 * A {@link Future} that is {@link Runnable}. Successful execution of
 * the <tt>run</tt> method causes completion of the <tt>Future</tt>
 * and allows access to its results.
 * @see FutureTask
 * @see Executor
 * @since 1.6
 * @author Doug Lea
 * @param <V> The result type returned by this Future's <tt>get</tt> method
 */
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

让 Executor 为您构造 FutureTask 的一个很好的理由是确保对 FutureTask 实例的引用不会超过一个。也就是说,Executor拥有这个实例。

于 2011-02-10T12:09:22.757 回答
16

Future只是界面。在幕后,实现是FutureTask.

您绝对可以FutureTask手动使用,但您将失去使用的优势Executor(池线程,限制线程等)。Using与使用 old和使用run方法FutureTask非常相似。Thread

于 2011-02-10T12:06:41.963 回答
8

如果您想更改其行为或稍后访问其 Callable,则只需要使用 FutureTask。对于 99% 的用途,只需使用 Callable 和 Future。

于 2011-02-10T12:07:56.480 回答
4

正如 Mark 和其他人所正确回答的那样,这Future是工厂的接口,FutureTask并且是Executor其工厂的有效接口;这意味着应用程序代码很少FutureTask直接实例化。为了补充讨论,我提供了一个示例,显示了FutureTask在 any 之外直接构造和使用的情况Executor

    FutureTask<Integer> task = new FutureTask<Integer>(()-> {
        System.out.println("Pretend that something complicated is computed");
        Thread.sleep(1000);
        return 42;
    });

    Thread t1 = new Thread(()->{
        try {
            int r = task.get();
            System.out.println("Result is " + r);
        } catch (InterruptedException | ExecutionException e) {}
    });
    Thread t2 = new Thread(()->{
        try {
            int r = task.get();
            System.out.println("Result is " + r);
        } catch (InterruptedException | ExecutionException e) {}
    });
    Thread t3 = new Thread(()->{
        try {
            int r = task.get();
            System.out.println("Result is " + r);
        } catch (InterruptedException | ExecutionException e) {}
    });

    System.out.println("Several threads are going to wait until computations is ready");
    t1.start();
    t2.start();
    t3.start();
    task.run(); // let the main thread to compute the value

在这里,FutureTask用作同步工具,类似CountdownLatch或类似的屏障原语。CountdownLatch它可以通过使用或锁和条件重新实现;FutureTask只是使它很好地封装,不言自明,优雅且代码更少。

另请注意,必须在任何线程中显式调用 FutureTask#run() 方法;周围没有执行者为您做这件事。在我的代码中,它最终由主线程执行,但是可以修改get()方法以run()在第一个线程调用时调用get(),因此第一个线程到达get(),它可以是 T1、T2 或 T3 中的任何一个,将对所有线程进行计算剩余线程。

关于这个想法 - 第一个线程请求结果将为其他线程进行计算,而并发尝试将被阻止 - 基于 Memoizer,请参阅“Java 并发实践”中第 108 页的 Memoizer 缓存示例。

于 2017-06-27T21:22:58.657 回答
0

如前所述,但不是笼统地说,而是更多的技术术语,因为 FutureTask 实现了 RunnableFuture,你可以使用它来调用它

FutureTask<T> result = new FutureTask<T>(new #YourClassImplementingCallable());
Thread t1= new Thread(result);
t1.start();
Object<T> obj = result.get();

这与旧的可运行文件更加内联,但它也具有通过回调返回结果的能力。

FutureTask 比 Future 更强大的功能在于它对线程有更多的控制,而不是简单地提交一个可调用的 Future 并让 executor 处理线程。

就像你可以在这里调用 t1.join()。

于 2021-05-22T07:35:39.153 回答