スレッドコラボレーション
前回の記事では、スレッドプールを正しく作成する方法、タスクを送信するためのThreadPoolExecutor execute(実行可能タスク)のメソッドを紹介しましたが、以下では実行タスクの戻り結果について説明します。
1.タスクの実行結果を取得するにはどうすればよいですか?
Javaは、ThreadPoolExecutorによって提供される3つのsubmit()メソッドとFutureTaskツールクラスを使用して、タスクの実行結果を取得します。
1.1 3つのsubmit()メソッド
// 1. 提交Runnable任务
Future<?> submit(Runnable task);
// 2.提交Callable任务
<T> Future<T> submit(Callable<T> task);
// 3.提交Runnable任务及结果引用
<T> Future<T> submit(Runnable task, T result);
3つのsubmit()メソッドの戻り値は、5つのメソッドを持つFutureインターフェースであることがわかります。
// 取消任务
boolean cancel( boolean mayInterruptIfRunning);
// 判断任务是否已取消
boolean isCancelled();
// 判断任务是否已结束
boolean isDone();
// 阻塞式获得任务执行结果,如果线程任务还没执行结束,那么调用get()的线程会被阻塞,直到任务执行结束才会唤醒
get();
// 阻塞式获得任务执行结果,支持超时
get(long timeout, TimeUnit unit);
3つの送信方法の違いはパラメーターが異なるため、それらについて個別に説明しましょう。
- メソッドパラメータはRunnableであり、Runnableインターフェイスのrun()メソッドには戻り値がないため、このFutureはThread.join()と同様にタスクの終了のみをアサートできます。
- Callableインターフェースのcall()メソッドには戻り値があるため、Futureインターフェースのget()メソッドを呼び出して、タスクの実行結果を取得できます。
- 最後に、パラメータを送信する3番目の方法はRunableとresultです。Runableには戻り値はありませんが、Futureインターフェースを呼び出すget()メソッドは結果の結果を返します。
結果は、メインスレッドとサブスレッド間のブリッジに相当します。これにより、メインサブスレッドがデータを共有できます。
次に、3番目の送信メソッドの古典的な使用方法を示します:Runnableインターフェースの実装クラスは、パラメーター化されたコンストラクターTask(Result r)を宣言します。Taskオブジェクトが作成されると、結果オブジェクトが渡され、Taskクラスで実行できます。 ()メソッドの結果に対してさまざまな操作が実行されました。
ExecutorService executor = Executors.newFixedThreadPool(1);
// 创建Result对象r
Result r = new Result();
r.setAAA(a);
// 提交任务
Future<Result> future = executor.submit(new Task(r), r);
Result fr = future.get();
// 下面等式成立
fr === r;
fr.getAAA() === a;
fr.getXXX() === x
class Task implements Runnable{
Result r;
//通过构造函数传入result
Task(Result r){
this.r = r;
}
void run() {
//可以操作result
a = r.getAAA();
r.setXXX(x);
}
}
1.2 submit()メソッドとexecute()メソッドの違いは何ですか?
- パラメータは同じではありません。submit()の3番目の送信メソッドは、resultパラメータで渡すことができます。
- submit()によって実行されたスレッドは結果Futureを返すことができますが、execute()は実行結果を返すことができません。
- 例外処理は同じではなく、submit()メソッドです。Futureのget()メソッドの例外を処理して、スレッドを試行、キャッチ、および実行できます。タスクの実行中に例外が発生すると、スレッドが終了し、例外を処理できなくなります。先に試して、スレッド実行メソッドで例外をキャッチしてください。
//submit方法处理异常
try {
fs.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
executorService.shutdownNow();
e.printStackTrace();
return;
}
//execute方法处理异常,只能在Runnable接口或其他的run方法中处理异常。
threadPoolTaskExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("sleep后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
1.3 FutureTaskツール
上記のFutureはインターフェースであり、FutureTaskは実際にはツールクラスであり、2つのコンストラクターがあります。
FutureTask(Callable<V> callable);
FutureTask(Runnable runnable, V result);//和上面的一样。
実際、FutureTaskはRunnableインターフェースとFutureインターフェースを実装しています。一方で、それはタスクとしてスレッドに送信して実行できます。また、タスクの実行結果も取得できます。次のサンプルコードを見ることができます:
// 创建FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(()-> 1+2);
// 创建线程池
ExecutorService es = Executors.newCachedThreadPool();
// 提交FutureTask
es.submit(futureTask);
// 获取计算结果
Integer result = futureTask.get();
2.最適な「水を燃やしてお茶を作る」プログラムを実現する
湯を沸かしてお茶を淹れる最適なプロセスは次のようになります:
同時プログラミングのコア問題:分業、同期、相互排除、最初に行うことは分業、つまりタスクを効率的に分解してスレッドに割り当てる方法です
もちろん、最善の方法は、ツールクラスCountDownLatch、join()を以前のjavaツールクラスで使用できます。また、Futureを使用して実装できます。
最初に2つのFutureTasks-ft1とft2を作成します。ft1はやかんの洗浄、お湯の沸騰、お茶の製造を終了し、ft2はティーポットの洗浄、ティーカップの洗浄、および茶葉の取り出しを終了します。もちろん、ft1がお茶を淹れる前に、ft2を実行する必要があります。
// 创建任务T2的FutureTask
FutureTask<String> ft2 = new FutureTask<>(new T2Task());
// 创建任务T1的FutureTask
FutureTask<String> ft1 = new FutureTask<>(new T1Task(ft2));
// 线程T1执行任务ft1
Thread T1 = new Thread(ft1);
T1.start();
// 线程T2执行任务ft2
Thread T2 = new Thread(ft2);
T2.start();
// 等待线程T1执行结果
System.out.println(ft1.get());
// T1Task需要执行的任务:
// 洗水壶、烧开水、泡茶
class T1Task implements Callable<String>{
FutureTask<String> ft2;
// T1任务需要T2任务的FutureTask
T1Task(FutureTask<String> ft2){
this.ft2 = ft2;
}
@Override
String call() throws Exception {
System.out.println("T1:洗水壶...");
TimeUnit.SECONDS.sleep(1);
System.out.println("T1:烧开水...");
TimeUnit.SECONDS.sleep(15);
// 获取T2线程的茶叶
String tf = ft2.get();//拿到ft2的执行结果,没执行结束,阻塞等待(只是拿ft2的执行结果,并不是让ft2任务才执行)
System.out.println("T1:拿到茶叶:"+tf);
System.out.println("T1:泡茶...");
return "上茶:" + tf;
}
}
// T2Task需要执行的任务:
// 洗茶壶、洗茶杯、拿茶叶
class T2Task implements Callable<String> {
@Override
String call() throws Exception {
System.out.println("T2:洗茶壶...");
TimeUnit.SECONDS.sleep(1);
System.out.println("T2:洗茶杯...");
TimeUnit.SECONDS.sleep(2);
System.out.println("T2:拿茶叶...");
TimeUnit.SECONDS.sleep(1);
return "龙井";
}
}
// 一次执行结果:
T1:洗水壶...
T2:洗茶壶...
T1:烧开水...
T2:洗茶杯...
T2:拿茶叶...
T1:拿到茶叶:龙井
T1:泡茶...
上茶:龙井