先物のList
を返すメソッドがあります
List<Future<O>> futures = getFutures();
ここで、すべてのフューチャの処理が正常に完了するか、フューチャによって出力が返されるタスクのいずれかが例外をスローするまで待機します。 1つのタスクが例外をスローした場合でも、他の未来を待つ意味はありません。
簡単なアプローチは
wait() {
For(Future f : futures) {
try {
f.get();
} catch(Exception e) {
//TODO catch specific exception
// this future threw exception , means somone could not do its task
return;
}
}
}
しかし、ここでの問題は、たとえば、4番目のフューチャーが例外をスローした場合、最初の3つのフューチャーが利用可能になるまで不必要に待つことです。
これを解決するには?カウントダウンラッチは何らかの形で役立ちますか? Java docが言うので、Future isDone
を使用できません
boolean isDone()
Returns true if this task completed. Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method will return true.
CompletionService を使用して、フューチャーの準備ができ次第、フューチャーを受け取り、そのうちの1つが例外をスローした場合、処理をキャンセルできます。このようなもの:
Executor executor = Executors.newFixedThreadPool(4);
CompletionService<SomeResult> completionService =
new ExecutorCompletionService<SomeResult>(executor);
//4 tasks
for(int i = 0; i < 4; i++) {
completionService.submit(new Callable<SomeResult>() {
public SomeResult call() {
...
return result;
}
});
}
int received = 0;
boolean errors = false;
while(received < 4 && !errors) {
Future<SomeResult> resultFuture = completionService.take(); //blocks if none available
try {
SomeResult result = resultFuture.get();
received ++;
... // do something with the result
}
catch(Exception e) {
//log
errors = true;
}
}
タスクの1つがエラーをスローした場合、まだ実行中のタスクをキャンセルするようにさらに改善できると思います。
編集:ここでより包括的な例を見つけました: http://blog.teamlazerbeez.com/2009/04/29/Java-completionservice/
Java 8を使用している場合、CompletableFutureおよび CompletableFuture.allOf を使用してこれを簡単に行うことができます。提供されたCompletableFutureがすべて完了しました。
// Waits for *all* futures to complete and returns a list of results.
// If *any* future completes exceptionally then the resulting future will also complete exceptionally.
public static <T> CompletableFuture<List<T>> all(List<CompletableFuture<T>> futures) {
CompletableFuture[] cfs = futures.toArray(new CompletableFuture[futures.size()]);
return CompletableFuture.allOf(cfs)
.thenApply(ignored -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
ExecutorCompletionService を使用できます。ドキュメントには、正確なユースケースの例も含まれています。
代わりに、タスクセットの最初のnull以外の結果を使用し、例外が発生した場合は無視し、最初のタスクの準備ができたら他のすべてのタスクをキャンセルするとします。
void solve(Executor e, Collection<Callable<Result>> solvers) throws InterruptedException {
CompletionService<Result> ecs = new ExecutorCompletionService<Result>(e);
int n = solvers.size();
List<Future<Result>> futures = new ArrayList<Future<Result>>(n);
Result result = null;
try {
for (Callable<Result> s : solvers)
futures.add(ecs.submit(s));
for (int i = 0; i < n; ++i) {
try {
Result r = ecs.take().get();
if (r != null) {
result = r;
break;
}
} catch (ExecutionException ignore) {
}
}
} finally {
for (Future<Result> f : futures)
f.cancel(true);
}
if (result != null)
use(result);
}
ここで注意すべき重要な点は、ecs.take()が最初に送信されたタスクだけでなく、最初のcompletedタスクを取得することです。したがって、実行を完了する(または例外をスローする)順序で取得する必要があります。
CompletableFuture
in Java 8を使用
// Kick of multiple, asynchronous lookups
CompletableFuture<User> page1 = gitHubLookupService.findUser("Test1");
CompletableFuture<User> page2 = gitHubLookupService.findUser("Test2");
CompletableFuture<User> page3 = gitHubLookupService.findUser("Test3");
// Wait until they are all done
CompletableFuture.allOf(page1,page2,page3).join();
logger.info("--> " + page1.get());
CompletableFuturesのリストを組み合わせたい場合、これを行うことができます:
List<CompletableFuture<Void>> futures = new ArrayList<>();
// ... Add futures to this ArrayList of CompletableFutures
// CompletableFuture.allOf() method demand a variadic arguments
// You can use this syntax to pass a List instead
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[futures.size()]));
// Wait for all individual CompletableFuture to complete
// All individual CompletableFutures are executed in parallel
allFutures.get();
Future&CompletableFutureの詳細、役立つリンク:
1。将来: https://www.baeldung.com/Java-future
2。 CompletableFuture: https://www.baeldung.com/Java-completablefuture
3。 CompletableFuture: https://www.callicoder.com/Java-8-completablefuture-tutorial/
Java 8を使用していて、CompletableFuture
sを操作したくない場合、ストリーミングを使用してList<Future<T>>
の結果を取得するツールを作成しました。重要なのは、map(Future::get)
がスローされるときに禁止されていることです。
public final class Futures
{
private Futures()
{}
public static <E> Collector<Future<E>, Collection<E>, List<E>> present()
{
return new FutureCollector<>();
}
private static class FutureCollector<T> implements Collector<Future<T>, Collection<T>, List<T>>
{
private final List<Throwable> exceptions = new LinkedList<>();
@Override
public Supplier<Collection<T>> supplier()
{
return LinkedList::new;
}
@Override
public BiConsumer<Collection<T>, Future<T>> accumulator()
{
return (r, f) -> {
try
{
r.add(f.get());
}
catch (InterruptedException e)
{}
catch (ExecutionException e)
{
exceptions.add(e.getCause());
}
};
}
@Override
public BinaryOperator<Collection<T>> combiner()
{
return (l1, l2) -> {
l1.addAll(l2);
return l1;
};
}
@Override
public Function<Collection<T>, List<T>> finisher()
{
return l -> {
List<T> ret = new ArrayList<>(l);
if (!exceptions.isEmpty())
throw new AggregateException(exceptions, ret);
return ret;
};
}
@Override
public Set<Java.util.stream.Collector.Characteristics> characteristics()
{
return Java.util.Collections.emptySet();
}
}
これには、C#のように機能するAggregateException
が必要です
public class AggregateException extends RuntimeException
{
/**
*
*/
private static final long serialVersionUID = -4477649337710077094L;
private final List<Throwable> causes;
private List<?> successfulElements;
public AggregateException(List<Throwable> causes, List<?> l)
{
this.causes = causes;
successfulElements = l;
}
public AggregateException(List<Throwable> causes)
{
this.causes = causes;
}
@Override
public synchronized Throwable getCause()
{
return this;
}
public List<Throwable> getCauses()
{
return causes;
}
public List<?> getSuccessfulElements()
{
return successfulElements;
}
public void setSuccessfulElements(List<?> successfulElements)
{
this.successfulElements = successfulElements;
}
}
このコンポーネントは、C#の Task.WaitAll とまったく同じように機能します。 CompletableFuture.allOf
(Task.WhenAll
と同等)と同じ動作をするバリアントに取り組んでいます
私がこれをした理由は、SpringのListenableFuture
を使用しており、より標準的な方法であるにもかかわらずCompletableFuture
に移植したくないからです。
多分これは役立つでしょう(何も生のスレッドに置き換えられないでしょう!)それぞれのFuture
男を別々のスレッドで実行することをお勧めします(それらは並列になります)、そしてエラーが発生したときはいつでも、マネージャー(Handler
クラス)に信号を送ります。
class Handler{
//...
private Thread thisThread;
private boolean failed=false;
private Thread[] trds;
public void waitFor(){
thisThread=Thread.currentThread();
List<Future<Object>> futures = getFutures();
trds=new Thread[futures.size()];
for (int i = 0; i < trds.length; i++) {
RunTask rt=new RunTask(futures.get(i), this);
trds[i]=new Thread(rt);
}
synchronized (this) {
for(Thread tx:trds){
tx.start();
}
}
for(Thread tx:trds){
try {tx.join();
} catch (InterruptedException e) {
System.out.println("Job failed!");break;
}
}if(!failed){System.out.println("Job Done");}
}
private List<Future<Object>> getFutures() {
return null;
}
public synchronized void cancelOther(){if(failed){return;}
failed=true;
for(Thread tx:trds){
tx.stop();//Deprecated but works here like a boss
}thisThread.interrupt();
}
//...
}
class RunTask implements Runnable{
private Future f;private Handler h;
public RunTask(Future f,Handler h){this.f=f;this.h=h;}
public void run(){
try{
f.get();//beware about state of working, the stop() method throws ThreadDeath Error at any thread state (unless it blocked by some operation)
}catch(Exception e){System.out.println("Error, stopping other guys...");h.cancelOther();}
catch(Throwable t){System.out.println("Oops, some other guy has stopped working...");}
}
}
上記のコードはエラーになります(チェックしませんでした)が、解決策を説明できるといいのですが。試してみてください。
CompletionServiceは、.submit()メソッドでCallableを取得し、.take()メソッドで計算された先物を取得できます。
忘れてはならないことの1つは、.shutdown()メソッドを呼び出してExecutorServiceを終了することです。また、executorサービスへの参照を保存した場合にのみこのメソッドを呼び出すことができるため、必ず参照を保持してください。
サンプルコード-一定数のワークアイテムを並行して処理する場合:
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
CompletionService<YourCallableImplementor> completionService =
new ExecutorCompletionService<YourCallableImplementor>(service);
ArrayList<Future<YourCallableImplementor>> futures = new ArrayList<Future<YourCallableImplementor>>();
for (String computeMe : elementsToCompute) {
futures.add(completionService.submit(new YourCallableImplementor(computeMe)));
}
//now retrieve the futures after computation (auto wait for it)
int received = 0;
while(received < elementsToCompute.size()) {
Future<YourCallableImplementor> resultFuture = completionService.take();
YourCallableImplementor result = resultFuture.get();
received ++;
}
//important: shutdown your ExecutorService
service.shutdown();
サンプルコード-並行して作業する作業項目の動的な数:
public void runIt(){
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
CompletionService<CallableImplementor> completionService = new ExecutorCompletionService<CallableImplementor>(service);
ArrayList<Future<CallableImplementor>> futures = new ArrayList<Future<CallableImplementor>>();
//Initial workload is 8 threads
for (int i = 0; i < 9; i++) {
futures.add(completionService.submit(write.new CallableImplementor()));
}
boolean finished = false;
while (!finished) {
try {
Future<CallableImplementor> resultFuture;
resultFuture = completionService.take();
CallableImplementor result = resultFuture.get();
finished = doSomethingWith(result.getResult());
result.setResult(null);
result = null;
resultFuture = null;
//After work package has been finished create new work package and add it to futures
futures.add(completionService.submit(write.new CallableImplementor()));
} catch (InterruptedException | ExecutionException e) {
//handle interrupted and assert correct thread / work packet count
}
}
//important: shutdown your ExecutorService
service.shutdown();
}
public class CallableImplementor implements Callable{
boolean result;
@Override
public CallableImplementor call() throws Exception {
//business logic goes here
return this;
}
public boolean getResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
}
/**
* execute suppliers as future tasks then wait / join for getting results
* @param functors a supplier(s) to execute
* @return a list of results
*/
private List getResultsInFuture(Supplier<?>... functors) {
CompletableFuture[] futures = stream(functors)
.map(CompletableFuture::supplyAsync)
.collect(Collectors.toList())
.toArray(new CompletableFuture[functors.length]);
CompletableFuture.allOf(futures).join();
return stream(futures).map(a-> {
try {
return a.get();
} catch (InterruptedException | ExecutionException e) {
//logger.error("an error occurred during runtime execution a function",e);
return null;
}
}).collect(Collectors.toList());
};
これらを含むユーティリティクラスがあります。
@FunctionalInterface
public interface CheckedSupplier<X> {
X get() throws Throwable;
}
public static <X> Supplier<X> uncheckedSupplier(final CheckedSupplier<X> supplier) {
return () -> {
try {
return supplier.get();
} catch (final Throwable checkedException) {
throw new IllegalStateException(checkedException);
}
};
}
それができたら、静的インポートを使用して、次のようなすべての先物を簡単に待つことができます。
futures.stream().forEach(future -> uncheckedSupplier(future::get).get());
次のようにすべての結果を収集することもできます。
List<MyResultType> results = futures.stream()
.map(future -> uncheckedSupplier(future::get).get())
.collect(Collectors.toList());
私の古い投稿を再訪し、あなたが別の悲しみを抱えていることに気づいただけです:
しかし、ここでの問題は、たとえば、4番目のフューチャーが例外をスローした場合、最初の3つのフューチャーが利用可能になるまで不必要に待つことです。
この場合、簡単な解決策はこれを並行して行うことです。
futures.stream().parallel()
.forEach(future -> uncheckedSupplier(future::get).get());
このように、最初の例外は、将来を停止しませんが、シリアルの例のようにforEachステートメントを壊しますが、すべてが並行して待機するため、最初の3つが完了するまで待つ必要はありません。