(ソースコードを見るために横画面電話がより便利です)
注意:Javaソースコード解析部を特別な命令がjava8バージョンに基づいてされていない場合。
注意:元のスレッドプール部を特別な指示がThreadPoolExecutorクラスを参照していない場合。
簡単な紹介
以前の私たちはプールで一般的なタスクのプロセス・スレッドを実行するために一緒に学んだが、実際にあなたがスレッドプールで実行されるタスクの結果を得るためにそれを使用することができ、タスクは将来(今後の課題)と呼ばれているタスクが存在し、それを達成する方法ですか?
この章を理解するのに役立ちますし、コードの反対側は、この章の弟トンを読む前に推奨され、比較的最初の訪問の前に学ぶために、比較的短い「彼自身を(続き)スレッドプールを書くのサイケJavaスレッドシリーズ」、書きました簡単に。
問題
(1)スレッドプールの次のタスクは、それを実装する方法ですか?
(2)私たちが学ぶことができる何より良いデザインパターンを?
(3)他のフレームワークを学ぶために、将来的に私たちを助けるためにそこに起こっていますか?
栗へ
私たちは、この章の内容を説明するために、一例で始まります。
私たちは、スレッドプールを定義し、将来のある時点で、5つのタスクは、0,1,2,3,4を返された5つのタスクを、提出するためにそれを使用し、我々は彼らの戻り値へのアクセス権を持って、累積演算を行います。
public class ThreadPoolTest02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 新建一个固定5个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
List<Future<Integer>> futureList = new ArrayList<>();
// 提交5个任务,分别返回0、1、2、3、4
for (int i = 0; i < 5; i++) {
int num = i;
// 任务执行的结果用Future包装
Future<Integer> future = threadPool.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("return: " + num);
// 返回值
return num;
});
// 把future添加到list中
futureList.add(future);
}
// 任务全部提交完再从future中get返回值,并做累加
int sum = 0;
for (Future<Integer> future : futureList) {
sum += future.get();
}
System.out.println("sum=" + sum);
}
}
ここでは、二つの質問を考慮してください。
(1)一般的なタスクを使用している場合は、どのようにどのくらいの時間については、書くには?
あなたが通常のタスクを使用する場合は、(最終号)の操作を蓄積するタスクに入れたが、とてもよく書かれていない、合計時間は、おそらく1秒より少しです。しかし、これには欠点があること自体は一緒に結合され、乗算疲れている場合、バックに変更するだけでなく、タスクの内容を変更するためのタスクと操作の蓄積されたコンテンツ。
(2)future.get()ループのため、どのくらいの時間程度にがある場合は?
私たちはまず、この質問に答えるのソースコード解析を見ないようにしましょう。
()メソッドを提出
方法を提出し、それはタスクの値を返す方法を持っているために提出され、内部使用(FutureTask)パッケージのため、次に実行するために次のタスク()を実行し、最終的に次のタスク自体を返すこと。
public <T> Future<T> submit(Callable<T> task) {
// 非空检测
if (task == null) throw new NullPointerException();
// 包装成FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
// 交给execute()方法去执行
execute(ftask);
// 返回futureTask
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
// 将普通任务包装成FutureTask
return new FutureTask<T>(callable);
}
ここでのデザインは、実際には、これらの2つの方法がAbstractExecutorServiceでテンプレートを使用する方法であり、この抽象クラスを、実行され、非常に賢いです。
継承システムFutureTaskを見てみましょう:
FutureTaskはRunnableFutureインタフェース、およびそれらの組み合わせが実行可能インターフェースと将来インターフェイスをRunnableFutureインタフェースする能力を実現し、かつ将来のインタフェースは、タスクの戻り値を取得する能力を提供します。
問題:提出()メソッドの代わりにRunnableFutureインターフェイスまたはクラスFutureTaskそれをなぜ未来のインターフェースを返しますか?
:これは、外部の発信者がただのget()機能(将来のインタフェース)を公開するために、)の結果は、(返さ提出する予定であるが、その実行()機能(Runaable Interface)を公開したくありませんでした。
FutureTaskクラス()メソッドを実行します
最後の章を学習した後、我々はそのexecute()メソッドの呼び出しはrun()メソッドの最後のタスクである知っている、我々は上記のタスクに渡し、最終的にはFutureTaskのパッケージになった、それが実行される()メソッドは、最後のFutureTaskの呼び出しrun()メソッドので、我々はそれに、この直接法を見てください。
public void run() {
// 状态不为NEW,或者修改为当前线程来运行这个任务失败,则直接返回
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
// 真正的任务
Callable<V> c = callable;
// state必须为NEW时才运行
if (c != null && state == NEW) {
// 运行的结果
V result;
boolean ran;
try {
// 任务执行的地方【本文由公从号“彤哥读源码”原创】
result = c.call();
// 已执行完毕
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
// 处理异常
setException(ex);
}
if (ran)
// 处理结果
set(result);
}
} finally {
// 置空runner
runner = null;
// 处理中断
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
あなたは、コードが比較的簡単で見ることができ、最初の状態が検知されない、タスク、最終処理結果や例外を行います。
ミッションは、ここで問題に欠けている、のは、コードまたは異常な処理結果を見てみましょう。
protected void setException(Throwable t) {
// 将状态从NEW置为COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 返回值置为传进来的异常(outcome为调用get()方法时返回的)
outcome = t;
// 最终的状态设置为EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
// 调用完成方法
finishCompletion();
}
}
protected void set(V v) {
// 将状态从NEW置为COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 返回值置为传进来的结果(outcome为调用get()方法时返回的)
outcome = v;
// 最终的状态设置为NORMAL
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// 调用完成方法
finishCompletion();
}
}
一見すると、これら2つの方法が同様に見える、差は、状態のうちの結果は、最終的finishCompletion()メソッドと呼ばれ、同じと同じではないではないです。
private void finishCompletion() {
// 如果队列不为空(这个队列实际上为调用者线程)
for (WaitNode q; (q = waiters) != null;) {
// 置空队列
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
// 调用者线程
Thread t = q.thread;
if (t != null) {
q.thread = null;
// 如果调用者线程不为空,则唤醒它
// 【本文由公从号“彤哥读源码”原创】
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
// 钩子方法,子类重写
done();
// 置空任务
callable = null; // to reduce footprint
}
全体のrun()メソッドは、結論します:
(1)FutureTask処理動作状態が終了状態からNEW-> COMPLETING-> NORMAL、NEW-> COMPLETING-から異常動作状態の終了を実行して、状態制御タスクを有する> EXCEPTIONAL。
(2)タスクを実行するためのスレッドランナーを保存FutureTask、それはプール内のスレッドです。
(3)呼び出し側スレッドがウェイターキューに格納され、それは、それに設定されている場合?
(4)タスクが完了すると、状態変化の状態を設定することに加えて、呼び出し側のスレッドを覚ます必要があります。
(ウェイター)でFutureTaskを行うときに、呼び出し元のスレッドが保存されていますか?ビューのコンストラクタ:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
該当する情報は見つかりませんでした、呼び出し側は、get()メソッドを呼び出していない場合、我々はそれから想像し、これは将来の課題は、通常のタスクと違いはありませんではないでしょうか?確かに、ヘクタール、これだけ呼び出して取得()メソッドは、必要にFutureTaskに呼び出し側のスレッドが保存されています。
それでは、get()メソッドを見てみましょうことは何幽霊です。
FutureTaskクラス()メソッドを取得します
タスクが完了しない場合はget()メソッドが呼び出されると、それが使命の終わりまでブロックされます。
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 如果状态小于等于COMPLETING,则进入队列等待
if (s <= COMPLETING)
s = awaitDone(false, 0L);
// 返回结果(异常)
return report(s);
}
タスク状態が補完-----------------等しいよりも小さい場合、プロセスは、キューを待つように進み、非常に明確ではありません。
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 我们这里假设不带超时
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
// 处理中断
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
// 4. 如果状态大于COMPLETING了,则跳出循环并返回
// 这是自旋的出口
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 如果状态等于COMPLETING,说明任务快完成了,就差设置状态到NORMAL或EXCEPTIONAL和设置结果了
// 这时候就让出CPU,优先完成任务
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 1. 如果队列为空
else if (q == null)
// 初始化队列(WaitNode中记录了调用者线程)
q = new WaitNode();
// 2. 未进入队列
else if (!queued)
// 尝试入队
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 超时处理
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
// 3. 阻塞当前线程(调用者线程)
else
// 【本文由公从号“彤哥读源码”原创】
LockSupport.park(this);
}
}
ここでは、あなたが()タスクが実行されていないのgetを呼び出すとき、つまり、そのステータスはNEWで、我々は上記は再びロジックを行く1,2,3,4マークしてみているとします。
(1)最初のサイクル、新しい状態は、直接1に、WaitNode内のキューと、呼び出し元スレッドパッケージを初期化します。
(2)第二サイクルは、状態キューが呼び出し側スレッドを含むチームにWaitNodeを聞かせて、2で、空ではない、新しいものです。
(3)第3のサイクルでは、ステータスは、ブロック、キューが空ではなく、3 ATに、チームになっている、呼び出し側スレッドNEWです。
(4)タスクが完了されている間、解析の実行に応じて()メソッドは呼び出し側の糸パークを解除結局ます後、即ち、三つの目覚めされると仮定する。
(5)状態を完了するより確かに大きい第4サイクル、ループを終了し、返します。
質問:あなたはループのためにプロセス全体をコントロールしたいなぜ、ここですべてのステップは大丈夫析出書くことですか?
:あなたが書くことですが、コードが非常に長くなります思い付くことができれば、すべてのアクションは状態の再検討状況する必要があるため、変更されていません。ここで分析のみを取得()のステータスNEW、他の州が独立して検証することができ、彼らが正しいを確保することができ、あるいは2つのスレッドが(スキルがブレークポイント)交差実行します。
OK、それから返され、その後、ここで扱う最終的な結果であるする方法を見て。
private V report(int s) throws ExecutionException {
Object x = outcome;
// 任务正常结束
if (s == NORMAL)
return (V)x;
// 被取消了
if (s >= CANCELLED)
throw new CancellationException();
// 执行异常
throw new ExecutionException((Throwable)x);
}
あなたは以前の分析を実行したときに例外はそれが使用する内部の結果、上の異常があるとき、タスクの実行を覚えておいてください。
通常の実行が終了した場合(1)、タスクは、戻り値を返します。
例外が完了した場合(2)、パッケージ化ExecutionException例外がスローされます。
このように、異常が現れたスレッドは、呼び出し元のスレッドに戻り、何の成功はありません最後にタスクの実行を知らない、発信者のような一般的なタスクを実行するようにしないことがあります。
他の
FutureTaskの戻り値がタスクに加えて得ることができ、それはまた、タスクの実行をキャンセルすることができます。
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
タスクを処理するための実行スレッドによって中断され、興味のある学生が自分を分析することができたままにしておきます。
答えが始まります
どのくらいの時間についてのforループにfuture.getを()、がある場合は?
:おそらく、それぞれのタスクを送信するので、タスクが完了するまで、我々は、呼び出し元のスレッドをブロックしなければならない、5秒よりも少しだろう、各タスクの実行には、1秒以上であるので、合計時間は5秒とより多くのポイントです。
概要
(1)今後の課題は、FutureTaskに一般的なタスクをパッケージ化することによって達成されます。
(2)だけでなくFutureTask、ならびに知覚異常なタスクの実行によって実行されるタスクの結果を取得し、さらにタスクをキャンセルします。
非常に重要な設計パターンであるテンプレート法の数で定義された(3)AbstractExecutorService。
(4)FutureTaskは、このデザインコンセプトが表示されますときに我々は後で、ネッティー、ダボに学び、実際に異常呼の典型的な実装です。
卵
RPCフレームワークの非同期呼び出しを達成する方法ですか?
:RPCフレームワーク同期呼び出しを起動する通常の方法は、非同期呼び出しは、実際には、彼らは基本的に達成するためにFutureTaskの方法を使用することで非同期呼び出し、です。
一般に、スレッドによって(私たちは、リモートスレッドを呼び出す)が同期呼び出しである場合、リモートインターフェイスを呼び出すには、結果が戻されるまで、リモート呼び出し側スレッドの結果を待ってブロックするスレッドに直接呼び出し元、それは非同期呼び出しである場合、リモートの結果は、()メソッドは、同じ呼び出し側スレッドにブロックします返さ前にこのFutureXxxが呼び出される場合には、将来の最初のリターンは、当然のことながら、遠隔FutureXxx事に結果を得ることができます。
興味のある学生は、非同期についての準備をするために行くことができます(RpcContextの未来に投げ込まれる)ダボを呼び出します。
私は公共の番号「トンの弟は、ソースを読んで、」ビューのソースコードよりシリーズの関心を歓迎し、海の兄弟トンソースが一緒に泳ぎます。