首先,我必须说我对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()方法中完成的。
它工作得很好,但我不确定这是否正确。 是一个做我想要的好方法吗?如果没有,你能给我一些提示来增强我的代码(并尽可能保持简单)。
答案 0 :(得分:43)
最好使用get()
方法。
futureOne.get();
futureTwo.get();
两个都等待来自线程的通知它已完成处理,这样可以节省您正在使用的繁忙等待计时器,这对您来说既不高效也不优雅。
作为奖励,您拥有API get(long timeout, TimeUnit unit)
,它允许您定义线程休眠和等待响应的最长时间,否则继续运行。
有关详细信息,请参阅Java API。
答案 1 :(得分:18)
以上FutureTask
的使用是可以容忍的,但绝对不是惯用的。您实际上是围绕提交给FutureTask
的额外 ExecutorService
。 FutureTask
将Runnable
视为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,以便尽快计算出来 期货已全部结束。