这是一个使用java.util.concurrent.FutureTask的好方法吗?

时间:2009-02-11 11:04:25

标签: java multithreading concurrency futuretask

首先,我必须说我对API java.util.concurrent很新,所以也许我正在做的事情是完全错误的。

我想做什么?

我有一个基本上运行2个独立处理的Java应用程序(称为 myFirstProcess mySecondProcess ),但这些处理必须同时运行。

所以,我试着这样做:

public void startMyApplication() {
    ExecutorService executor = Executors.newFixedThreadPool(2);
    FutureTask<Object> futureOne = new FutureTask<Object>(myFirstProcess);
    FutureTask<Object> futureTwo = new FutureTask<Object>(mySecondProcess);
    executor.execute(futureOne);
    executor.execute(futureTwo);
    while (!(futureOne.isDone() && futureTwo.isDone())) {
        try {
            // I wait until both processes are finished.
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    logger.info("Processing finished");
    executor.shutdown();
    // Do some processing on results
    ...
}

myFirstProcess mySecondProcess 是实现Callable<Object>的类,其中所有处理都是在call()方法中完成的。

它工作得很好,但我不确定这是否正确。 是一个做我想要的好方法吗?如果没有,你能给我一些提示来增强我的代码(并尽可能保持简单)。

6 个答案:

答案 0 :(得分:43)

最好使用get()方法。

futureOne.get();
futureTwo.get();

两个都等待来自线程的通知它已完成处理,这样可以节省您正在使用的繁忙等待计时器,这对您来说既不高效也不优雅。

作为奖励,您拥有API get(long timeout, TimeUnit unit),它允许您定义线程休眠和等待响应的最长时间,否则继续运行。

有关详细信息,请参阅Java API

答案 1 :(得分:18)

以上FutureTask的使用是可以容忍的,但绝对不是惯用的。您实际上是围绕提交给FutureTask额外 ExecutorServiceFutureTaskRunnable视为ExecutorService。在内部,它会将您的FutureTask - 作为 - Runnable包裹在新的FutureTask中,并将其作为Future<?>返回给您。

相反,您应该将Callable<Object>个实例提交给CompletionService。您可以通过Callable删除两个submit(Callable<V>),然后转身并拨打CompletionService#take()两次(每次提交Callable一次)。这些调用将阻塞直到一个,然后其他提交的任务完成。

鉴于您已经拥有Executor,请在其周围构建一个新的ExecutorCompletionService并将任务放在那里。不要旋转和睡觉等待; CompletionService#take()将阻止,直到您的任务之一完成(已完成运行或已取消)或等待take()的线程中断。

答案 2 :(得分:17)

Yuval的解决方案很好。作为替代方案,您也可以这样做:

ExecutorService executor = Executors.newFixedThreadPool();
FutureTask<Object> futureOne = new FutureTask<Object>(myFirstProcess);
FutureTask<Object> futureTwo = new FutureTask<Object>(mySecondProcess);
executor.execute(futureOne);
executor.execute(futureTwo);
executor.shutdown();
try {
  executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
  // interrupted
}

这种方法有什么优势?除了这样你停止执行者接受任何更多的任务(你可以用另一种方式做到这一点)之外,真的没有太大区别。我倾向于更喜欢这个成语。

此外,如果get()抛出异常,您可能会在代码的一部分中假定两个任务都已完成,这可能是错误的。

答案 3 :(得分:7)

您可以使用invokeall(Collection ....)方法

package concurrent.threadPool;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class InvokeAll {

    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newFixedThreadPool(5);
        List<Future<java.lang.String>> futureList = service.invokeAll(Arrays.asList(new Task1<String>(),new Task2<String>()));

        System.out.println(futureList.get(1).get());
        System.out.println(futureList.get(0).get());
    }

    private static class Task1<String> implements Callable<String>{

        @Override
        public String call() throws Exception {
            Thread.sleep(1000 * 10);
            return (String) "1000 * 5";
        }

    }

    private static class Task2<String> implements Callable<String>{

        @Override
        public String call() throws Exception {
            Thread.sleep(1000 * 2);
            int i=3;
            if(i==3)
                throw new RuntimeException("Its Wrong");
            return (String) "1000 * 2";
        }

    }
}

答案 4 :(得分:5)

如果您有兴趣同时启动线程,或等待它们完成然后再进行一些处理,您可能需要使用CyclicBarrier。 有关更多信息,请参阅javadoc。

答案 5 :(得分:2)

如果您的futureTasks超过2,请考虑[ListenableFuture][1]

  

当多个操作应该在另一个操作后立即开始   开始 - “扇出” - ListenableFuture正常工作:触发全部   请求的回调。稍微多做一点,我们可以“粉丝入侵”或者   触发一个ListenableFuture,以便尽快计算出来   期货已全部结束。