歴史的に、Java はプリエンプティブな限定割り込みを提供しようと試みてきましたが、以前に導入された放棄された Thread.stop、Thread.suspend、Thread.resume など、多くの問題がありました。一方で、Java アプリケーション コードの堅牢性を考慮して、プログラミングのしきい値は低くなり、基礎となるメカニズムを知らないプログラマーが意図せずにシステムに損傷を与える可能性が低くなります。
現在、Java のスレッド スケジューリングはプリエンプティブ割り込みを提供せず、協調割り込みを使用します。実際、協調割り込みの原理は非常に単純で、割り込みを示す特定のマークをポーリングするというもので、通常のコードに実装できます。
たとえば、次のコード:
volatile bool は中断されます。
//…
while(!isInterrupted) {
計算();
}
ただし、上記のコードの問題も明らかです。計算実行時間が比較的長い場合、割り込みへの応答が間に合わなくなります。一方、ポーリングを使用してフラグ変数をチェックすることにより、待機やスリープなどのスレッドのブロック操作を中断する方法はありません。
上記の考え方を引き続き使用する場合、割り込みにタイムリーに応答したい場合は、仮想マシンの下部にあるスレッド スケジューリングを通じてマーク変数をチェックする必要があります。はい、これは実際に JVM で行われます。
以下は java.lang.Thread のソース コードからの抜粋です。
パブリック静的ブール値中断() {
currentThread().isInterrupted(true) を返します。
}
//…
プライベートネイティブブール値 isInterrupted(boolean ClearInterrupted);
isInterrupted がネイティブ メソッドとして宣言されていることがわかります。これは、基礎となる JVM 実装に依存します。
実際、JVM はスレッドごとに内部的に割り込みフラグを維持します。ただし、アプリケーションはこの割り込み変数に直接アクセスできないため、次の方法で動作する必要があります。
パブリック クラス スレッド {
//割り込みマークを設定する
パブリック void 割り込み() { ... }
//割り込みマークの値を取得
public boolean isInterrupted() { ... }
//割り込みマークをクリアし、最後の割り込みマークの値を返す
public static boolean中断() { ... }
}
通常、スレッドの割り込みメソッドを呼び出しても、すぐには割り込みは発生せず、JVM 内で割り込みフラグが設定されるだけです。したがって、割り込みフラグをチェックすることにより、アプリケーションは何か特別なことを行うか、割り込みを完全に無視することができます。
JVM がこの粗雑な割り込みメカニズムのみを提供する場合、割り込み変数を定義してポーリングするアプリケーション独自の方法と比較して、基本的には何の利点もないと考えるかもしれません。
JVM の内部割り込み変数の主な利点は、特定の状況での自動「割り込みトラップ」をシミュレートするメカニズムを提供することです。
スレッドのスケジューリング (待機、スリープ、結合など) を伴うブロック呼び出しを実行するときに、割り込みが発生すると、ブロックされたスレッドは「できるだけ早く」 InterruptedException をスローします。したがって、次のコード フレームワークを使用して、スレッド ブロック割り込みを処理できます。
試す {
//待つか、スリープするか、参加するか
}
catch(InterruptedException e) {
// いくつかの割り込み処理作業
}
「可能な限り高速」とは、JVM がスレッド スケジューリングの合間に割り込み変数をチェックすることを意味します。その速度は、JVM の実装とハードウェアのパフォーマンスに依存します。
ただし、特定のスレッド ブロック操作の場合、JVM は自動的に InterruptedException をスローしません。たとえば、特定の I/O 操作や内部ロック操作などです。このタイプの操作では、他の方法で割り込みをシミュレートできます。
1) java.io の非同期ソケット I/O
ソケットの読み取りおよび書き込み時、InputStream および OutputStream の読み取りおよび書き込みメソッドはブロックされて待機しますが、Java 割り込みには応答しません。ただし、ソケットの close メソッドを呼び出した後、ブロックされたスレッドは SocketException をスローします。
2) セレクターを使用して実装された非同期 I/O
スレッドが Selector.select (java.nio.channels 内) でブロックされている場合、wakeup メソッドを呼び出すと ClosedSelectorException 例外が発生します。
3) ロックの取得
スレッドが内部ロックの取得を待機している場合、それを中断することはできません。ただし、Lock クラスの lockInterruptible メソッドを使用すると、ロックの待機中に割り込み機能を提供できます。
さらに、タスクとスレッドが分離されているフレームワークでは、タスクは通常、どのスレッドによって呼び出されるのかがわからないため、割り込みを処理するための呼び出しスレッドの戦略もわかりません。したがって、タスクがスレッド中断フラグを設定した後、タスクがキャンセルされるという保証はありません。したがって、プログラミングには 2 つの原則があります。
1) スレッドの割り込みポリシーを知らない限り、スレッドを割り込まないでください。
この原則は、Executer などのフレームワークでスレッドの割り込みメソッドを直接呼び出すのではなく、Future.cancel などのメソッドを使用してタスクをキャンセルする必要があることを示しています。
2) タスクコードは、実行スレッドにとって割り込みが何を意味するかを推測すべきではありません。
この原則は、一般的なコードが InterruptedException に遭遇した場合、それをキャッチして「飲み込む」のではなく、上位のコードにスローし続ける必要があることを示しています。
つまり、Java の非プリエンプティブ割り込みメカニズムでは、従来のプリエンプティブ割り込みの考え方を変更し、その本質を理解した上で対応する原則とパターンをプログラミングに採用する必要があります。