スレッドタスクのキャンセル

操作が正常に完了する前に、外部コードが操作を「完了」状態にできる場合、その操作はキャンセル可能であると言われます。操作をキャンセルする理由はさまざまです。

ユーザーがキャンセルを要求しました。ユーザーは、GUI プログラムの「キャンセル」ボタンをクリックするか、JMX (Java Management Extensions) などの管理インターフェースを介してキャンセル要求を送信します。

時間制限のあるアクション。たとえば、アプリケーションは有限時間内に問題空間を検索し、この時間内で最適な解決策を選択する必要があります。タイマーが切れたら、すべての検索タスクをキャンセルする必要があります。

アプリケーションイベント。たとえば、アプリケーションは問題空間を分解して検索するため、さまざまなタスクが問題空間のさまざまな領域を検索できます。タスクの 1 つが解決策を見つけても、他のすべてのタスクは引き続き検索します

Thread.stop やsuspend などのメソッドはそのようなメカニズムを提供しますが、いくつかの重大な欠点があるため、これらは避けるべきです。これらの問題の詳細な説明については、http://java.sun.com/j2se/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html を参照してください。

キャンセルされます。

間違い。Web クローラーは関連するページを検索し、ページまたは概要データをハードディスクに保存します。1 つのクローラ タスクでエラーが発生した場合 (ディスク容量がいっぱいであるなど)、すべての検索タスクがキャンセルされ、その時点で現在のステータスが記録され、後で再開できるようになります。

閉鎖。プログラムまたはサービスがシャットダウンすると、処理中および処理待ちの作業に対して何らかのアクションを実行する必要があります。正常なシャットダウンでは、現在実行中のタスクは完了するまで実行され続けますが、即時シャットダウンでは、現在のタスクがキャンセルされる場合があります。

Java にはスレッドを先制的に安全に停止する方法はなく、したがって、タスクを先制的に安全に停止する方法もありません。取り消しを要求するタスクとコードがネゴシエートされたプロトコルに従うという協調メカニズムのみが存在します。

調整メカニズムの 1 つは「キャンセル要求済み」フラグを設定でき、タスクは定期的にフラグをチェックします。このフラグが設定されている場合、タスクは早期に終了します。この手法はリスト 7-1 のプログラムで使用されており、PrimeGenerator はキャンセルされるまで素数を列挙し続けます。cancel メソッドはキャンセルされたフラグを設定し、メイン ループは次の素数を検索する前に最初にこのフラグを確認します。(この手順が確実に機能するには、キャンセルされたフラグが揮発性である必要があります。)

パブリック クラス PrimeGenerator は Runnable {を実装します。

@GuardedBy("これ")

private Final List<BigInteger> 素数

=new ArrayList<BigInteger>();

プライベートの揮発性ブール値がキャンセルされました。

public void run(){

BigInteger p =BigInteger。一;

while (Iキャンセル){

p =p。nextProbablePrime();

同期(これ){

素数。追加(p);

}

}

}

public void cancel(){キャンセル =true;}

public synchronized List<BigInteger>get(){

新しい ArrayList<BigInteger>(素数) を返します。

}

}

                                                                      

リスト 7-2 は、このクラスの使用例を示しています。つまり、素数生成器を 1 秒間実行してからキャンセルします。素数ジェネレータは、通常、ちょうど 1 秒間実行した後は停止しません。これは、キャンセルが要求された瞬間と、実行メソッド ループ内の次のチェックとの間に遅延が生じる可能性があるためです。cancel メソッドは、finally ブロックによって呼び出されるため、sleep が呼び出されたときに素数生成器の実行が中断された場合でも、確実に実行がキャンセルされます。cancel が呼び出されないと、素数を検索するスレッドが永久に実行され、CPU クロック サイクルが消費され、JVM が正常に終了できなくなります。

                                 リスト 7-2 1 秒間だけ実行される素数生成器                  

List<BigInteger>aSecond0fPrimes() throws InterruptedException {

PrimeGenerator ジェネレーター =new PrimeGenerator();

新しいスレッド (ジェネレーター)。始める();

試す {

秒。スリープ(1);

}ついに {

発生器。キャンセル();

}

リターンジェネレータ。得る();

}

キャンセル可能なタスクには、キャンセル ポリシー (キャンセル ポリシー) が必要です。キャンセル ポリシーでは、キャンセル操作の「方法」、「いつ」、「何を」が詳細に定義されます。つまり、他のコードがタスクのキャンセルをどのように (どのように) 要求するかが定義されます。タスク、タスクのキャンセルが要求されたかどうかをいつ (When) チェックするか、キャンセル要求に応じてどのような (What) アクションを実行する必要があるかを確認します。

実際の支払い停止小切手の例を考えてみましょう。銀行は通常、支払い停止リクエストの送信方法、これらのリクエストを処理する際にどのような対応保証が必要か、支払いが中断された場合にどのようなプロセスに従う必要があるか(取引に関与する他の銀行や手数料の支払い口座への通知など)を指定します。評価)。これらのプロセスと保証を総合して、小切手支払いのキャンセル ポリシーが形成されます。

PrimeGenerator は単純なキャンセル戦略を使用します。クライアント コードは cancel を呼び出してキャンセルを要求します。PrimeGenerator は、素数を検索する前にまずキャンセル リクエストがあるかどうかを確認し、存在する場合は終了します。

 邪魔をして

PrimeGenerator のキャンセル メカニズムにより、最終的には素数の検索タスクが終了しますが、終了プロセスには一定の時間がかかります。ただし、このメソッドを使用するタスクが BlockingQueue.put などのブロッキング メソッドを呼び出す場合、タスクがキャンセル フラグをまったくチェックしないため、タスクが終了しないという、より深刻な問題が発生する可能性があります。

コード リスト 7-3 の BrokenPrimeProducer は、この問題を示しています。プロデューサ スレッドは素数を生成し、それらをブロッキング キューに入れます。プロデューサの速度がコンシューマの処理速度を超えると、キューがいっぱいになり、put メソッドがブロックされます。プロデューサーが put メソッドでブロックされている場合、コンシューマーがプロデューサー タスクをキャンセルしたい場合はどうなりますか? コンシューマーは cancel メソッドを呼び出してキャンセルされたフラグを設定できますが、プロデューサーは回復できないため、現時点ではこのフラグを確認できません。ブロックされた put メソッドから (この時点でコンシューマーがキューから素数を取得するのを停止しているため、put メソッドはブロックされたままになります)。

これ。キュー = キュー;

}

public void run(){

試す {

BigInteger p =BigInteger。一;

その間 (キャンセルされました)

列。put(p=p.nextProbablePrime());

}catch (InterruptedException が消費されました){}

}

public void cancel(){キャンセル =true;}

}

void ConsumerPrimes() は InterruptedException をスローします {

BlockingQueue<BigInteger>primes =???;

BrokenPrimeProducer プロデューサー =new BrokenPrimeProducer(primes);

プロデューサー。始める();

試す {

while (needMorePrimes())

Consumer(primes.take());

}ついに {

プロデューサー。キャンセル();

}

}

                                                                      

特別なブロッキング ライブラリの一部のメソッドは割り込みをサポートします。スレッドの中断は、スレッドが別のスレッドに通知して、適切または可能な場合に現在の作業を停止し、代わりに他の作業を実行するように指示できる協調メカニズムです。

Java の API または言語仕様では、割り込みはキャンセルのセマンティクスとは関連付けられていませんが、実際には、キャンセル以外の操作で割り込みを使用することは不適切であり、大規模なアプリケーションをサポートすることは困難です。

各スレッドにはブール値の割り込みステータスがあります。スレッドが中断されると、スレッドの中断ステータスが true に設定されます。リスト 7-4 に示すように、Thread には、スレッドを中断し、スレッド中断ステータスをクエリするためのメソッドが含まれています。割り込みメソッドはターゲット スレッドに割り込むことができ、isInterrupted メソッドはターゲット スレッドの割り込みステータスを返すことができます。静的割り込みメソッドは、現在のスレッドの割り込みステータスをクリアし、以前の値を返します。これが、割り込みステータスをクリアする唯一の方法です。

                                 プログラムリスト7-4 スレッド内の割り込みメソッド                  

パブリック クラス スレッド {

public void中断(){…}

public boolean isInterrupted(){……}

パブリック静的ブール値中断(){…}

...

                                                                                             

Thread.sleep や Object.wait などのブロッキング ライブラリ メソッドは、スレッドが中断されたときにチェックし、中断が検出された場合は早期に戻ります。割り込みに応じて実行される操作には、割り込みステータスのクリア、および割り込みによりブロック操作が途中で終了したことを示す InterruptedException のスローが含まれます。JVM は、ブロッキング メソッドが割り込みを検出する速度を保証しませんが、実際には応答速度は依然として非常に高速です。

スレッドが非ブロッキング状態で割り込まれると、その割り込みステータスが設定され、取り消される操作に対して割り込みステータスがチェックされて、割り込みが発生したかどうかが判断されます。このようにして、割り込み操作は「スティッキー」になります。InterruptedException がトリガーされない場合、割り込み状態が明示的にクリアされるまで、割り込み状態は維持されます。

割り込みの呼び出しは、ターゲット スレッドの進行中の作業を直ちに停止することを意味するのではなく、割り込みを要求するメッセージを渡すだけです。

割り込み操作の正しい理解は、実行中のスレッドに実際に割り込むのではなく、割り込み要求を送信するだけであり、スレッドは次の適切な瞬間にそれ自体に割り込みます。(これらの瞬間はキャンセルポイントとも呼ばれます)。wait、sleep、join などの一部のメソッドはこの種のリクエストを厳密に処理し、割り込みリクエストを受信したり、実行開始時に設定されている割り込みステータスを見つけたりすると、例外をスローします。適切に設計されたメソッドは、呼び出し元のコードが割り込み要求の処理を実行できるようにする限り、そのような要求を完全に無視できます。メソッドの設計が適切でないと、割り込み要求がマスクされ、コール スタック内の他のコードが割り込み要求に応答できなくなる可能性があります。

静的割り込みを使用すると、現在のスレッドの割り込みステータスがクリアされるため、使用する場合は注意が必要です。Interrupted の呼び出し時に true が返された場合は、割り込みをマスクする場合を除き、割り込みを処理する必要があります。コード リスト 5-10 に示すように、InterruptedException をスローするか、再度 Interrupt を呼び出して割り込み状態を復元することができます。

BrokenPrimeProducer は、一部のカスタム キャンセル メカニズムがブロッキング ライブラリ関数とうまく連携しない理由を示しています。タスク コードが割り込みに応答できる場合、キャンセル メカニズムとして割り込みを使用し、多くのライブラリ クラスで提供される割り込みサポートを利用できます。

多くの場合、割り込みはキャンセルを実装する最も論理的な方法です。

BrokenPrimeProducer の問題は簡単に解決 (そして簡略化) できます。リスト 7-5 に示すように、ブール値フラグの代わりに割り込みを使用してキャンセルを要求します。ループの各反復において、割り込みが検出できる場所は 2 か所あります。1 つはブロッキング put メソッド呼び出しで、もう 1 つはループの先頭で割り込みステータスがクエリされるときです。ブロッキング put メソッドが呼び出されるため、ここでは明示的なチェックは必要ありませんが、チェックを実行すると、PrimeProducer は割り込みに対する応答性が高くなります。これは、タスクの完了後ではなく、素数の検索を開始する前に割り込みをチェックするためです。割り込み可能なブロック メソッドが十分に応答するほど頻繁に呼び出されない場合は、割り込みステータスを明示的にチェックすると役に立ちます。

                                    リスト 7-5 割り込みによるキャンセル                        

class PrimeProducer extends Thread {

プライベート最終 BlockingQueue<BigInteger> キュー;

PrimeProducer(BlockingQueue<BigInteger>キュー){

これ。キュー = キュー;

}

public void run(){

試す {

BigInteger p =BigInteger。一;

while (!Thread.currentThread().isInterrupted())

列。put (p =p.nextProbablePrime());

}catch (InterruptedException が消費されました){

/* スレッドの終了を許可します */

}

}

public void cancel(){interrupt();}

}

                                                                     

中断戦略

タスクにキャンセル ポリシーが含まれている必要があるのと同様に、スレッドにも中断ポリシーが含まれている必要があります。割り込みポリシーは、スレッドが割り込み要求を解釈する方法、つまり割り込み要求が見つかったときに (必要に応じて) どのような作業を行うべきか、割り込みに対してどの作業単位がアトミックであるか、および割り込みにどれだけ早く応答するかを指定します。

最も合理的な中断戦略は、何らかの形式のスレッド レベルまたはサービス レベルのキャンセルです。つまり、できるだけ早く終了し、必要に応じてクリーンアップし、スレッドが終了したことを所有者に通知します。さらに、サービスの一時停止やサービスの再開など、他の割り込みポリシーを確立することもできますが、非標準の割り込みポリシーを含むスレッドまたはスレッド プールの場合、これらのポリシーを認識できるタスクでのみ使用できます。

割り込みに対するタスクとスレッドの反応を区別することが重要です。割り込みリクエストには 1 つ以上のレシーバーを含めることができます。スレッド プール内の特定のワーカー スレッドを割り込むことは、「現在のタスクをキャンセル」し、「ワーカー スレッドを閉じる」ことを意味します。

タスクは、それが所有するスレッドではなく、何らかのサービス (スレッド プールなど) が所有するスレッドで実行されます。スレッドの所有者ではないコード (たとえば、スレッド プールの場合、スレッド プール実装の外部にあるコード) の場合は、スレッドを所有するコードが割り込みに応答できるように、割り込み状態を保存するように注意する必要があります。たとえ「所有者ではない」コードでも応答できる場合でも。(家の掃除をするときは、オーナーが不在でも、その間に届いた郵便物は捨てずにしまって、オーナーが戻ってきたら渡してください。雑誌は読むことができます。)

そのため、ほとんどのブロッキング ライブラリ関数は、割り込みに応じて InterruptedException をスローするだけです。これらは自分が所有するスレッドで実行されることはないため、タスクまたはライブラリ コードに対して最も合理的なキャンセル ポリシーを実装します。つまり、できるだけ早く実行フローを終了し、呼び出し元に割り込み情報を渡し、呼び出しスタックの上位コードが実行できるようにします。さらなる行動を起こします。

割り込み要求が検出された場合、タスクはすべての操作を中止する必要はありません。より適切な時点まで割り込み要求の処理を延期できます。したがって、割り込み要求を記憶し、現在のタスクの完了後に InterruptedException をスローするか、割り込み要求を受信したことを示す必要があります。この技術により、更新プロセスが中断された場合でもデータ構造が破損しないことが保証されます。

タスクが、サービスに含まれる特定の中断ポリシーを持つサービスで実行されるように特に設計されていない限り、タスクは、タスクを実行するスレッドの中断ポリシーについていかなる仮定も立てるべきではありません。タスクが割り込みをキャンセルとして扱うか、または他の割り込み応答操作として扱うかに関係なく、実行中のスレッドの割り込み状態を保持するように注意する必要があります。呼び出し元に InterruptedException を渡す以外のことを行う必要がある場合は、InterruptedException をキャッチした後に中断された状態を復元する必要があります。

糸。currentThread()。割り込み();

タスク コードがそれを実行するスレッドの割り込みポリシーについて仮定すべきでないのと同様に、キャンセル操作を実行するコードもスレッドの割り込みポリシーについて仮定すべきではありません。スレッドは、シャットダウン メソッドなどの適切なキャンセル メカニズムでスレッドの中断ポリシー情報をカプセル化できる所有者によってのみ中断される必要があります。

各スレッドには独自の割り込みポリシーがあるため、そのスレッドにとって割り込みが何を意味するかを理解していない限り、スレッドを割り込まないでください。

Java にはプリエンプティブな割り込みメカニズムがなく、開発者が InterruptedException を処理する必要があるため、批評家は Java の割り込み機能を嘲笑しています。ただし、割り込み要求の処理を延期することで、開発者はより柔軟な割り込みポリシーを策定でき、アプリケーションが応答性と堅牢性の間で適切なバランスを達成できるようになります。

   応答割り込み

セクション 5.4 では、 Thread.sleep や BlockingQueue.put などの割り込み可能なブロック関数を呼び出すときに InterruptedException を処理するための 2 つの実用的な戦略があります。

(おそらくタスク固有のクリーンアップを実行した後) 例外を渡し、メソッドを割り込み可能なブロッキング メソッドにもします。

• 呼び出しスタックの上位コードが割り込みステータスを処理できるように、割り込みステータスを復元します。

InterruptedException を渡すことは、リスト 7-6 の getNextTask に示すように、 throws 節に InterruptedException を追加するのと同じくらい簡単です。

                  コード リスト 7-6 は呼び出し元に InterruptedException を渡します。                              

BlockingQueue<Task>キュー;

public タスク getNextTask() が InterruptedException をスローする {`

InterruptedException を渡したくない場合、または渡せない場合 (おそらく Runnable でタスクを定義することによって)、割り込み要求を保存する別の方法を見つける必要があります。標準的な方法は、再度中断を呼び出して中断された状態を復元することです。たとえば、catch ブロックで例外をキャッチし、何も行わなかった場合、スレッドの割り込み戦略をコードに実装しない限り、InterruptedException をシールドすることはできません。PrimeProducer は割り込みをシールドしますが、これはスレッドが終了しつつあることをすでに認識しているためであり、呼び出しスタックには割り込み情報を知る必要がある上位コードがありません。ほとんどのコードはどのスレッドで実行されるかわからないため、割り込みステータスを保存する必要があります。

スレッド割り込みを実装するコードのみを割り込みキャストにアセンブルできます。通常のタスクとグループコードでの入札

割り込み要求はマスクしないでください

キャンセルをサポートしていないものの、割り込み可能なブロッキング メソッドを呼び出すことができる一部の操作では、それらのメソッドを。この場合、コード リスト 7-7 に示すように、中断された状態をローカルに保存し、InterruptedException がキャッチされたときではなく、戻る前に状態を復元する必要があります。割り込みステータスが時期尚早に設定されると、無限ループが発生する可能性があります。これは、ほとんどの割り込み可能なブロック メソッドが開始時に割り込みステータスをチェックし、ステータスが設定されていることが判明するとすぐに InterruptedException をスローするためです。(通常、割り込み可能なメソッドは、ブロックしたり重要な作業を実行したりする前に、まず割り込みをチェックするため、できるだけ早く割り込みに応答できます)。

public Task getNextTask(BlockingQueue<Taskgt;queue){

ブール値が中断されました =false;

試す {

一方 (true){

試す {

帰りの列。取った();

}catch (InterruptedException e){

中断 = true;

// 再試行する

}

}

}ついに {

もし(中断された場合)

糸。currentThread()。割り込み();

}

}

コードが割り込み可能なブロッキング メソッドを呼び出していない場合でも、タスク コードで現在のスレッドの割り込みステータスをポーリングすることで割り込みに応答することができます。適切なポーリング頻度を選択するには、効率と応答性の間のトレードオフが必要です。応答性が必要な場合、実行に時間がかかり、割り込みに応答しないメソッドは呼び出すべきではないため、呼び出せるライブラリ コードに制限が課されます。

中断状態以外の他の状態もキャンセル処理に関与する可能性があります。割り込みはスレッドの注意を引くために使用でき、割り込まれたスレッドによって保持される情報は、割り込まれたスレッドにさらなる命令を提供できます。(この情報にアクセスするときは、必ず同期を使用してください。)

たとえば、ThreadPoolExecutor が所有するワーカー スレッドが割り込みを検出すると、スレッド プールがシャットダウン中であるかどうかを確認します。存在する場合は、終了する前にスレッド プールのクリーンアップを実行します。そうでない場合は、スレッド プールを適切なサイズに復元するために新しいスレッドを作成する可能性があります。

  例:クロノラン

多くの問題は決して解決できません (たとえば、すべての素数を列挙するなど)。また、すぐに答えられる問題によっては、決して答えられない場合もあります。このような場合、「回答を検索するのに最大 10 分かかる」または「10 分以内に見つかる回答を列挙する」を指定できると非常に便利です。

リスト 7-2 の aSecondOfPrimes メソッドは、PrimeGenerator を開始し、1 秒後に中断します。PrimeGenerator は停止するまでに 1 秒以上かかる場合がありますが、最終的には割り込みを検出して停止し、スレッドを終了させます。タスク実行のもう 1 つの側面は、タスクの実行中に例外がスローされるかどうかを知りたいことです。

PrimeGenerator が指定された制限時間内に未チェックの例外をスローした場合、プライム ジェネレーターは例外を明示的に処理しない別のスレッドで実行されるため、例外は無視される可能性があります。

指定された時間、任意の Runnable を実行する例をリスト 7-8 に示します。呼び出し側スレッドでタスクを実行し、指定された間隔で実行した後にタスクを中断するキャンセル タスクをスケジュールします。これにより、例外は timedRun の呼び出し元によってキャッチされるため、タスクからチェックされていない例外がスローされる問題が解決されます。

キャンセル実行。スケジュール(new Runnable(){

public void run(){タスクスレッド. 割り込み();}

}、タイムアウト、ユニット);

r. 走る ( ) ;

}

これは非常に単純なアプローチですが、スレッドを中断する前にその中断ポリシーを知っておく必要があるというルールに違反します。timedRun はどのスレッドからも呼び出すことができるため、呼び出し元のスレッドの割り込みポリシーを知る方法がありません。タスクがタイムアウト前に完了した場合、timedRun が呼び出し元に戻った後に、timedRun が存在するスレッドを中断するキャンセルされたタスクが開始されます。この場合、どのようなコードが実行されるかはわかりませんが、結果は悪いものになるはずです。(このリスクを回避するには、スケジュールによって返される ScheduledFuture を使用してこのキャンセル タスクをキャンセルできます。この方法は実行可能ですが、非常に複雑です。)

さらに、タスクが割り込みに応答しない場合、timedRun は、指定された制限時間を超えている (またはまだ制限時間を超えていない) 可能性があるタスクが終了するまで戻りません。限られた時間だけ実行されるサービスが指定時間内に戻らない場合、呼び出し元に悪影響を及ぼします。

リスト 7-9 は、aSecondOfPrimes の例外処理の問題と前の解決策の問題を解決します。タスクを実行するスレッドには独自の実行ポリシーがあり、タスクが割り込みに応答しない場合でも、時間実行メソッドは呼び出し元に戻ることができます。タスク スレッドの開始後、timedRun は時限結合メソッドを実行します。join が戻った後、タスクで例外がスローされたかどうかを確認し、スローされた場合は、timedRun を呼び出したスレッドで例外を再スローします。Throwable は 2 つのスレッド間で共有されるため、変数は volatile として宣言され、タスク スレッドから timedRun スレッドに安全にポストできるようになります。

public void run(){

{rを試してください。走る() ;}

catch (Throwable t){this. t =t;}

}

void rethrow(){

if ( t != null)

スロー洗濯Throwable(t);

}

}

RethrowableTask タスク =new RethrowableTask();

最終スレッド taskThread = 新しいスレッド (タスク);

タスクスレッド。始める();

キャンセル実行。スケジュール(new Runnable(){

public void run(){タスクスレッド. 割り込み();}

}、タイムアウト、ユニット);

タスクスレッド。join(unit.toMillis(タイムアウト));

タスク。再スロー();

}

                                                                      

この例のコードは前の例の問題を解決しますが、時限結合に依存しているため、結合の欠点があります。スレッドが正常に終了したため、または結合がタイムアウトしたため、実行制御が戻ったのかを知ることが不可能です。 。

 Future経由でのキャンセル

私たちは、タスクのライフサイクルを管理し、例外を処理し、キャンセルを実装するために、Future という抽象メカニズムを使用しました。多くの場合、独自のクラスを作成するよりも既存のライブラリのクラスを使用する方が良いため、引き続き Future とタスク実行フレームワークを使用して timedRun を構築します。

ExecutorService.submit は、タスクを説明する Future を返します。Future には cancel メソッドがあり、キャンセル操作が成功したかどうかを示すブール値パラメーター MayInterruptIfRunning があります。(これは、タスクが割り込みを検出して処理できるかどうかではなく、タスクが割り込みを受信できるかどうかのみを示します。)mayInterruptIfRunning が true で、タスクが現在スレッドで実行されている場合、このスレッドは割り込み可能です。このパラメータが false の場合、「タスクが開始されていない場合は実行しない」ことを意味し、このメソッドは割り込みを処理しないタスクに使用する必要があります。

スレッドの中断戦略がわからない場合は、スレッドを中断しないでください。では、どのような状況で、パラメータを true に指定して cancel を呼び出すことができますか? タスクを実行するスレッドは、標準の Executor によって作成されます。割り込みを渡すタスクはキャンセルされるため、タスクが標準の Executor で実行されている場合は、mayInterruptIfRunning を設定し、Future 経由でタスクをキャンセルできます。タスクをキャンセルしようとする場合、スレッド プールに直接割り込むことはお勧めできません。割り込み要求が到着したときにどのタスクが実行されているかがわからないためです。キャンセルできるのはタスクの Future を通じてのみです。これは、タスクを作成するときに割り込みをキャンセル要求として扱うもう 1 つの理由です。タスクはタスクの Future を通じてキャンセルできます。

コード リスト 7-10 は、timedRun の別のバージョンを示しています。タスクを ExecutorService に送信し、スケジュールされた Future.get を通じて結果を取得します。get が戻り時に TimeoutException をスローした場合、タスクは Future を通じてキャンセルされます。(コードを簡素化するために、このバージョンの timedRun は、完了したタスクをキャンセルしても効果がないため、finally ブロック内で直接 Future.cancel を呼び出します。) タスクがキャンセルされる前に例外をスローした場合、その例外を処理するのは呼び出し側の責任です。リスト 7-10 に、もう 1 つの優れたプログラミング手法を示します。結果が不要になったタスクをキャンセルします。(同じ手法がリスト 6-13 とリスト 6-16 でも使用されています。)

            リスト 7-10 では、Future を使用してタスクをキャンセルします                        

public static void timedRun(Runnable r,

長いタイムアウト、TimeUnit 単位)

中断例外をスローします {

将来のタスク =taskExec。送信(r);

試す {

タスク。get(タイムアウト, ユニット);

}catch (タイムアウト例外 e){

// 次のタスクはキャンセルされます

}catch (ExecutionException e){

∥ タスクで例外がスローされた場合は、例外を再スローします。

throw launderThrowable(e.getCause());

}ついに {

//タスクが終了した場合、キャンセル操作を実行しても効果はありません

task.cancel(true);//タスクが実行中の場合、タスクは中断されます

}

}

                                                                      

Future.get が InterruptedException または TimeoutException をスローしたとき、結果が不要であることがわかっている場合は、Future.cancel を呼び出してタスクをキャンセルできます。

 無停電ブロッキングを処理する

Java ライブラリでは、多くのブロッキング メソッドが早期に返すか InterruptedException をスローすることで割り込み要求に応答するため、開発者はキャンセル要求に応答するタスクを簡単に構築できます。ただし、すべてのブロッキング メソッドまたはブロッキング メカニズムが割り込みに応答できるわけではありません。同期ソケット I/O を実行するか、組み込みロックの取得を待機することによってスレッドがブロックされている場合、割り込み要求はスレッドの割り込みステータスを設定することしかできず、それ以外の場合は何も設定できません。他の効果。中断不可能な操作によりブロックされたスレッドについては、中断と同様の手段を使用してこれらのスレッドを停止できますが、そのためにはスレッドがブロックされた理由を知る必要があります。

java.io パッケージの同期ソケット I/O。サーバー アプリケーションでは、ブロッキングの最も一般的な形式は、I/O であり、ソケットへの読み取りと書き込みです。InputStream や OutputStream の読み取りや書き込みなどのメソッドは割り込みに応答しませんが、基になるソケットを閉じることで、読み取りや書き込みなどのメソッドの実行によってブロックされたスレッドは SocketException をスローできます。

java.io パッケージの同期 I/O。InterruptibleChannel で待機しているスレッドを中断すると、ClosedByInterruptException がスローされ、リンクが閉じられます (これにより、このリンク上でブロックされている他のスレッドも ClosedByInterruptException をスローします)。InterruptibleChannel を閉じると、リンク操作でブロックされたすべてのスレッドが AsynchronousCloseException をスローします。ほとんどの標準チャネルは InterruptibleChannel を実装しています。

セレクターの非同期 I/O。Selector.select メソッド (java.nio.channels 内) の呼び出し中にスレッドがブロックした場合、close メソッドまたは wakeup メソッドを呼び出すと、スレッドは ClosedSelectorException をスローして早期に戻ります。

ロックを取得します。スレッドが組み込みロックの待機中にブロックされている場合、スレッドは必ずロックを取得すると信じているため、割り込みに応答できなくなり、割り込み要求を無視します。ただし、 lockInterruptibly メソッドが Lock クラスで提供されており 、割り込みに応答しながらロックを待機できるようになります。第 13 章を参照してください。

コード リスト 7-11 の ReaderThread プログラムは、非標準のキャンセル操作をラップする方法を示しています。ReaderThread はソケット接続を管理し、ソケットから同期的にデータを読み取り、受信したデータを processBuffer に渡します。ユーザーの接続を終了するかサーバーを閉じるために、ReaderThread は標準の割り込みを処理し、基になるソケットを閉じることができるように割り込みメソッドを書き換えます。 したがって、ReaderThread スレッドが読み取りメソッドでブロックされているか、割り込み可能なブロック メソッドでブロックされているかに関係なく、中断され、現在の作業の実行が停止される可能性があります。

public class ReaderThread extends Thread {

private Final Socket ソケット。

プライベート最終入力ストリーム。

public ReaderThread(Socket ソケット) が IOException をスローする {

これ。ソケット = ソケット;

これ。=ソケット内。入力ストリーム()を取得します;

}

パブリック void 割り込み(){

試す {

ソケット。近い();

}

catch (IOException は無視されました){}

ついに {

素晴らしい。割り込み();

}

}

public void run().{

試す {

byte[]buff=新しいバイト[BUFSZ];

一方 (true){

int カウント =in. 読み取り(バフ);

if (カウント < 0)

壊す;

else if (カウント >0)

processBuffer(buff, count);

}

catch (IOException e) {/* スレッドの終了を許可します */ }

}

}

                                                                      

 newTaskFor を使用して非標準のキャンセルをカプセル化する

newTaskFor メソッドを使用して、ReaderThread で非標準のキャンセルをカプセル化する手法をさらに最適化できます。

これは、ThreadPoolExecutor の Java 6 の新機能です。Callable が ExecutorService に送信されると、submit メソッドは Future を返し、この Future を使用してタスクをキャンセルできます。newTaskFor は、タスクを表す Future を作成するファクトリ メソッドです。newTaskFor は、Future と Runnable を拡張した RunnableFuture インターフェイスを返すこともできます (FutureTask によって実装されます)。

Future.cancel の動作は、タスクを表す Future をカスタマイズすることで変更できます。たとえば、カスタム キャンセル コードはログを実装したり、キャンセルされた操作に関する統計を収集したり、割り込みに応答しない一部の操作をキャンセルしたりできます。ReaderThreadは割り込みメソッドを書き換えることで、ソケットに基づいてスレッドをキャンセルすることができます。同様に、タスクのFuture.cancelメソッドを書き換えることでも同様の機能を実現できます。

CancelableTask インタフェースはリスト 7-12 の CancellableTask で定義されています。これは Callable を拡張し、RunnableFuture を構築するために cancel メソッドと newTask ファクトリ メソッドを追加します。CancelingExecutor は ThreadPoolExecutor を拡張し、newTaskFor を書き換えることによって CancelableTask が独自の Future を作成できるようにします。

パブリック インターフェイス CancelllableTask<T>extends Callable<T>{

無効キャンセル();

RunnableFuture<T>newTask();

}

@ThreadSafe

public class CancellingExecutor extends ThreadPoolExecutor {

...

protected<T>RunnableFuture< T>newTaskFor (Callable<T>callable){

if (CancelableTask の呼び出し可能なインスタンス)

return ((CancellableTask <T>)  callable)新しいwTask();

それ以外

スーパーを返します。新しいTaskFor(呼び出し可能);

}

}

パブリック抽象クラス SocketUsingTask<T>

CancelableTask<T>{ を実装します

@GuardedBy("this") プライベートソケット ソケット;

protected synchronized void setSocket(Socket s){socket =s;}

public synchronized void cancel(){

試す

if (ソケット !=null)

ソケット。近い();

}キャッチ(IOExceptionは無視されました){}

}

public RunnableFuture<T>newTask(){

return new FutureTask<T>(this){

public boolean cancel(boolean MayInterruptIfRunning){

試す {

ソケット使用タスク。これ。キャンセル();

}ついに {。

スーパーを返します。cancel(mayInterruptIfRunning);

1

SocketUsingTask は CancelableTask を実装し、ソケットを閉じて super.cancel を呼び出す Future.cancel を定義します。SocketUsingTask が独自の Future を介してキャンセルされると、基礎となるソケットが閉じられ、スレッドが中断されます。したがって、キャンセルに対するタスクの応答性が向上します。つまり、割り込み可能なメソッドの呼び出しがキャンセルに応答することが保証されるだけでなく、呼び出し可能なソケット I/O メソッドも呼び出すことができます。

おすすめ

転載: blog.csdn.net/2301_78064339/article/details/131027181