CompletionServiceを このブログ投稿 で見つけました。ただし、これは、標準のExecutorServiceに対するCompletionServiceの利点を実際に示しているわけではありません。どちらでも同じコードを書くことができます。それでは、CompletionServiceはいつ役に立つのでしょうか?
あなたはそれを非常に明確にするために短いコードサンプルを提供できますか?たとえば、このコードサンプルはCompletionServiceが不要な場所を示しています(= ExecutorServiceと同等)
ExecutorService taskExecutor = Executors.newCachedThreadPool();
// CompletionService<Long> taskCompletionService =
// new ExecutorCompletionService<Long>(taskExecutor);
Callable<Long> callable = new Callable<Long>() {
@Override
public Long call() throws Exception {
return 1L;
}
};
Future<Long> future = // taskCompletionService.submit(callable);
taskExecutor.submit(callable);
while (!future.isDone()) {
// Do some work...
System.out.println("Working on something...");
}
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
ExecutorService
を使用して、実行するタスクを送信したら、完了したタスクの結果を効率的に取得するために手動でコーディングする必要があります。
CompletionService
を使用すると、これはほとんど自動化されます。 1つのタスクのみを送信するため、提示したコードでは違いはあまり明確ではありません。ただし、送信するタスクのリストがあるとします。以下の例では、複数のタスクがCompletionServiceに送信されます。次に、(結果を取得するために)完了したタスクを見つけるのではなく、CompletionServiceインスタンスに結果が利用可能になったときに結果を返すように要求します。
public class CompletionServiceTest {
class CalcResult {
long result ;
CalcResult(long l) {
result = l;
}
}
class CallableTask implements Callable<CalcResult> {
String taskName ;
long input1 ;
int input2 ;
CallableTask(String name , long v1 , int v2 ) {
taskName = name;
input1 = v1;
input2 = v2 ;
}
public CalcResult call() throws Exception {
System.out.println(" Task " + taskName + " Started -----");
for(int i=0;i<input2 ;i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println(" Task " + taskName + " Interrupted !! ");
e.printStackTrace();
}
input1 += i;
}
System.out.println(" Task " + taskName + " Completed @@@@@@");
return new CalcResult(input1) ;
}
}
public void test(){
ExecutorService taskExecutor = Executors.newFixedThreadPool(3);
CompletionService<CalcResult> taskCompletionService = new ExecutorCompletionService<CalcResult>(taskExecutor);
int submittedTasks = 5;
for (int i=0;i< submittedTasks;i++) {
taskCompletionService.submit(new CallableTask (
String.valueOf(i),
(i * 10),
((i * 10) + 10 )
));
System.out.println("Task " + String.valueOf(i) + "subitted");
}
for (int tasksHandled=0;tasksHandled<submittedTasks;tasksHandled++) {
try {
System.out.println("trying to take from Completion service");
Future<CalcResult> result = taskCompletionService.take();
System.out.println("result for a task availble in queue.Trying to get()");
// above call blocks till atleast one task is completed and results availble for it
// but we dont have to worry which one
// process the result here by doing result.get()
CalcResult l = result.get();
System.out.println("Task " + String.valueOf(tasksHandled) + "Completed - results obtained : " + String.valueOf(l.result));
} catch (InterruptedException e) {
// Something went wrong with a task submitted
System.out.println("Error Interrupted exception");
e.printStackTrace();
} catch (ExecutionException e) {
// Something went wrong with the result
e.printStackTrace();
System.out.println("Error get() threw exception");
}
}
}
}
多くの詳細を省略する:
Javadocは、CompletionService
がいつExecutorService
が役に立たないのかという質問に最もよく答えると思います。
新しい非同期タスクの生成を、完了したタスクの結果の消費から切り離すサービス。
基本的に、このインターフェースにより、プログラムは、タスクの結果の他のコンシューマーを知らなくても、タスクを作成およびサブミットする(さらにはそれらのサブミットの結果を調べる)プロデューサーを持つことができます。一方、CompletionService
を認識しているコンシューマーは、タスクをサブミットするプロデューサーを認識せずにpoll
forまたはtake
を生成できます。
記録については、かなり遅いので間違っているかもしれませんが、そのブログ投稿のサンプルコードがメモリリークを引き起こすことはかなり確信しています。アクティブなコンシューマがExecutorCompletionService
の内部キューから結果を取り出すことなく、ブロガーがそのキューを排出することをどのように期待していたのかわかりません。
基本的に、複数のタスクを並行して実行し、それらを完了順に処理する場合は、CompletionService
を使用します。したがって、5つのジョブを実行すると、CompletionService
が最初のジョブを終了します。単一のタスクしかない例では、Executor
を送信する機能を除いて、Callable
に追加の値は与えられません。
まず、プロセッサ時間を無駄にしたくない場合は、使用しません
while (!future.isDone()) {
// Do some work...
}
私たちは使わなければなりません
service.shutdown();
service.awaitTermination(14, TimeUnit.DAYS);
このコードの悪い点は、ExecutorService
をシャットダウンすることです。引き続き作業したい場合(つまり、再帰的なタスクを作成する場合)、invokeAllまたはExecutorService
の2つの選択肢があります。
invokeAll
は、すべてのタスクが完了するまで待機します。 ExecutorService
は、結果を1つずつ取得またはポーリングする機能を付与します。
そして、完全に再帰的な例:
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_NUMBER);
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<String>(executorService);
while (Tasks.size() > 0) {
for (final Task task : Tasks) {
completionService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return DoTask(task);
}
});
}
try {
int taskNum = Tasks.size();
Tasks.clear();
for (int i = 0; i < taskNum; ++i) {
Result result = completionService.take().get();
if (result != null)
Tasks.add(result.toTask());
}
} catch (InterruptedException e) {
// error :(
} catch (ExecutionException e) {
// error :(
}
}
実行時に自分でそれを確認し、両方のソリューション(ExecutorserviceとCompletionservice)を実装してみてください。それらがどのように動作するかがわかり、どちらを使用するかがより明確になります。必要に応じてここに例があります http://rdafbn.blogspot.co.uk/2013/01/executorservice-vs-completionservice-vs.html
5つの長時間実行タスク(呼び出し可能タスク)があり、それらのタスクを実行サービスに送信したとします。ここで、5つのタスクすべてが競合するのを待たずに、いずれかのタスクが完了した場合にこれらのタスクで何らかの処理を行いたいとします。これは、将来のオブジェクトでポーリングロジックを記述するか、このAPIを使用することで実行できます。
completionserviceを使用する別の利点があります:パフォーマンス
future.get()
を呼び出すと、スピン待ちになります:
Java.util.concurrent.CompletableFuture
から
private Object waitingGet(boolean interruptible) {
Signaller q = null;
boolean queued = false;
int spins = -1;
Object r;
while ((r = result) == null) {
if (spins < 0)
spins = (Runtime.getRuntime().availableProcessors() > 1) ?
1 << 8 : 0; // Use brief spin-wait on multiprocessors
else if (spins > 0) {
if (ThreadLocalRandom.nextSecondarySeed() >= 0)
--spins;
}
長時間実行するタスクがある場合、これはパフォーマンスの障害になります。
completionserviceを使用すると、タスクが完了すると、結果がキューに登録され、より低いパフォーマンスでキューをポーリングできます。
completionserviceは、done
フックでラップタスクを使用してこれを実現します。
Java.util.concurrent.ExecutorCompletionService
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
タスクプロデューサーが結果に関心がなく、executorサービスによって実行された非同期タスクの結果を処理するのが別のコンポーネントの責任である場合、CompletionServiceを使用する必要があります。タスク結果プロセッサをタスクプロデューサから分離するのに役立ちます。例を参照してください http://www.zoftino.com/Java-concurrency-executors-framework-tutorial
package com.barcap.test.test00;
import Java.util.concurrent.*;
/**
* Created by Sony on 25-04-2019.
*/
public class ExecutorCompletest00 {
public static void main(String[] args) {
ExecutorService exc= Executors.newFixedThreadPool( 10 );
ExecutorCompletionService executorCompletionService= new ExecutorCompletionService( exc );
for (int i=1;i<10;i++){
Task00 task00= new Task00( i );
executorCompletionService.submit( task00 );
}
for (int i=1;i<20;i++){
try {
Future<Integer> future= (Future <Integer>) executorCompletionService.take();
Integer inttest=future.get();
System.out.println(" the result of completion service is "+inttest);
break;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
================================================== =====
package com.barcap.test.test00;
import Java.util.*;
import Java.util.concurrent.*;
/**
* Created by Sony on 25-04-2019.
*/
public class ExecutorServ00 {
public static void main(String[] args) {
ExecutorService executorService=Executors.newFixedThreadPool( 9 );
List<Future> futList= new ArrayList <>( );
for (int i=1;i<10;i++) {
Future result= executorService.submit( new Task00( i ) );
futList.add( result );
}
for (Future<Integer> futureEach :futList ){
try {
Integer inm= futureEach.get();
System.out.println("the result of future executorservice is "+inm);
break;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
================================================== =========
package com.barcap.test.test00;
import Java.util.concurrent.*;
/**
* Created by Sony on 25-04-2019.
*/
public class Task00 implements Callable<Integer> {
int i;
public Task00(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
System.out.println(" the current thread is "+Thread.currentThread().getName() +" the result should be "+i);
int sleepforsec=100000/i;
Thread.sleep( sleepforsec );
System.out.println(" the task complted for "+Thread.currentThread().getName() +" the result should be "+i);
return i;
}
}
================================================== =====================
executor完了サービスのログの違い:現在のスレッドはpool-1-thread-1です。結果は1でなければなりません。現在のスレッドはpool-1-thread-2です。結果は2です。現在のスレッドはpool-1-thread-です。 3結果は3になります現在のスレッドはpool-1-thread-4になります結果は4になります現在のスレッドはpool-1-thread-6になります結果に6になります現在のスレッドはpool-1-thread-5になります結果は5でなければなりません。現在のスレッドはpool-1-thread-7です。結果は7です。現在のスレッドはpool-1-thread-9です。結果は9です。現在のスレッドはpool-1-thread-8です。 8プール-1-スレッド-9用にコンパイルされたタスク結果は9になるはずです結果は9プール-1-スレッド-8用にコンパイルされたタスク結果8プール-1スレッド-7用にコンパイルされたタスク結果は、プール1スレッド6用にコンパイルされたタスク、結果6プール1スレッド5用にコンパイルされたタスク、結果5プール1スレッド4用にコンパイルされたタスク、結果4コンパイルされたタスクpool-1-thread-3の場合、結果は3になります。
現在のスレッドはpool-1-thread-1です。結果は1です。現在のスレッドはpool-1-thread-3です。結果は3です。現在のスレッドはpool-1-thread-2です。結果は2です。 threadはpool-1-thread-5です。結果は5です。現在のスレッドはpool-1-thread-4です。結果は4です。現在のスレッドはpool-1-thread-6です。結果は6です。現在のスレッドはpool-1-thread-7結果は7になります。現在のスレッドはpool-1-thread-8になります。結果は8になります。現在のスレッドはpool-1-thread-9になります。結果は9になります。 1-thread-9結果は、pool-1-thread-8にコンパイルされたタスク9になります。結果は、pool-1-thread-7にコンパイルされたタスク8になります。結果は、pool-1にコンパイルされたタスクになります。 thread-6結果は6になり、pool-1-thread-5にコンパイルされたタスクになります。結果は5 pool-1-thread-4にコンパイルされたタスクになります。結果は4 pool-1-thread-にコンパイルされたタスクになります。 3結果は3になりますol-1-thread-2結果は2である必要がありますpool-1-thread-1にコンパイルされたタスクは1である必要がありますfutureの結果は1です
================================================== =====
executorserviceの場合、結果はすべてのタスクがコンパイルされた後にのみ使用可能になります。
エグゼキュータ完了サービスは、結果が利用可能な場合、そのリターンを生成します。