JAvaマルチスレッドの割り込み(interrupt)問題


サマリ
Javaでは,1つのスレッドを停止させるには,(1)runメソッドの実行が完了するとスレッドが自然に終了するように終了フラグを採用する3つの方法がある.(2)stopを用いてスレッドを強制的に終了するが,この方法はセキュリティの問題でdeprecatedされている.(3)割り込み機構を用いる.
導入
1つ目の方法は特にないが,Runnableインタフェースを上書きする際にrunメソッドに状態識別ロジックを追加することにほかならない.たとえば:
public class MyThread extends Thread
{
    private boolean running;
    @Override
    public run(){
         if(running){
             //....business methods
         }
    }
}
以下、割り込みメカニズムについて詳細に説明する.ここでの割り込みは、あるスレッドでターゲットスレッドの割り込みフラグ位置ビットを取得し、ターゲットスレッドが割り込みチェックを行うときに、割り込みにどのように応答すべきかを判断するソフト割り込みである.特に、ターゲットスレッドを停止することができる.割り込みの限界は、デッドロック状態のスレッドには機能しません.
単純な原理:java.lang.Threadクラスには、スレッドの割り込みフラグ位置ビット:Thread.interrupt()メソッドが提供され、その関数は、public void interrupt();メソッドがスレッドの割り込みフラグ位置ビットを示す.java.lang.Threadクラスでは、割り込みフラグを確認する2つの方法もあります.
public static boolean interrupted()
{
    return currentThread().isInterrupted(true);
}
public boolean isInterrupted()
{
    return isInterrupted(false);
}
この2つのメソッドは、割り込み状態を確認するために同じオリジナルメソッドをそれぞれ呼び出し、異なる.一態様interrupted()は静的方法であり、Thread.interrupted()の方法を使用して呼び出すことができる.一方、isInterrupted()は、threadInstance.isInterrupted()を使用して呼び出される暗黙的なパラメータとしてインスタンスオブジェクトを必要とする.一方、interruptedは呼び出し後すぐにスレッド中断フラグビットをリセットし、isInterruptedはそうしません.
ブロックされていないスレッドの割り込みの問題:
Thread.interruptを呼び出してターゲットスレッドをフラグ位置ビットに割り込み、ターゲットスレッドで割り込みフラグビットを確認します.割り込みが検出された後にthrow new InterruptedException()を使用して例外を投げ出し、catch文を使用してこの例外をキャプチャすると、ここでいくつかの割り込みロジックを完了できます.特に、この方法を使用してスレッドを終了することができる.以下に、このような脱退方式の例を示す.
package com.kingbaron.jinterrupt;
/**
 * Created by kingbaron on 2016/3/17.
 */
public class UnblockingInterrupt extends Thread{
    @Override
    public void run(){
        super.run();
        try{
            for(int i=0;i<10000;i++)
            {
                if(Thread.interrupted())
                //if(this.isInterrupted())
                {
                    System.out.println("I'm interrupted and I'm going out");
                    throw new InterruptedException();
                }
                System.out.println("i="+(i+1));
            }
            System.out.println("I'm below the for clauses and I have no chance to run here");
        }catch(InterruptedException e){
            System.out.println("I'm in the catch clauses of the method UnblockingInterrupt.run()");
            e.printStackTrace();
        }
    }
}
package com.kingbaron.jinterrupt;
/**
 * Created by kingbaron on 2016/3/17.
 */
public class TestUnblockingInterrupt {
    public static void main(String[] args){
        try{
            UnblockingInterrupt thread=new UnblockingInterrupt();
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();
        }catch(InterruptedException e)
        {
            System.out.println("main catch");
            e.printStackTrace();
        }
    }
}
出力結果(キー)は次のとおりです.
i=277919
i=277920
I'm interrupted and I'm going out
I'm in the catch clauses of the method UnblockingInterrupt.run
java.lang.InterruptedException
at com.kingbaron.jinterrupt.UnblockingInterrupt.run(UnblockingInterrupt.java:16)
スレッドthreadの割り込みフラグビットがmainスレッドでthread.interruptを使用してセットされた後、threadスレッドのforループのif条件チェックが実行されると、Thread.interrupted()はtrueを返し、その中にInterruptedException異常を投げ出し、catch句を使用して異常をキャプチャする.runメソッドではcatch句の後に文がないため、したがって、スレッドはrunメソッドから返され、threadスレッドは終了する.
スレッドの割り込みの問題.
まず、1つのスレッドがブロック割り込み状態にある(実際には待機状態も含む)の理由としては、スレッドがthread.sleep、thread.join、thread.wait、1.5のcondition.await、および割り込み可能なチャネル上のI/O操作方法などを呼び出していることが一般的である.1つのスレッドがブロック状態にある場合、ブレークポイント位置がまた、異常が放出された直後にスレッドの割り込みフラグビットがリセットされ、すなわちfalseに再設定される.異常が放出されるのは、スレッドがブロック状態から覚醒し、スレッドが終了する前にプログラマに割り込み要求を処理するのに十分な時間を与えるためである.換言すれば、割り込みは、ブロック状態にあるスレッドのブロック状態を解除する.synchronizedは、ロックを取得する過程において割り込むことはできません.デッドロックが発生した場合、割り込むことはできません(後述のテスト例を参照してください).synchronized機能に似たreentrantLock.lock()メソッドも同様です.デッドロックが発生した場合、reentrantLock.lock()メソッドは終了できません.呼び出し時にブロックされると、ロックが取得されるまでブロックされます.ただし、タイムアウト付きtryLockメソッドreentrantLock.tryLock(long timeout,TimeUnit unit)を呼び出すとでは、スレッドが待機中に中断されると、InterruptedException例外が放出されます.これはプログラムがデッドロックを破ることができるため、非常に有用な特性です.reentrantLock.lockInterruptibly()を呼び出すこともできます.メソッドは、タイムアウトが無限に設定されたtryLockメソッドに相当します.中断されたスレッドが終了する必要はありません.中断されたスレッドを中断するのは、スレッドの注意を喚起するためだけです.中断されたスレッドは、中断にどのように対応するかを決定することができます.一部のスレッドは、中断を無視し、放出された例外を処理するために非常に重要です.その後も実行を続けますが、より一般的には、スレッドは割り込みを終了要求と見なします.このスレッドのrunメソッドは、次の形式に従います.
public void run() {
    try {
        ...
        /*
         *                   sleep、join、wait,        
         * !Thread.currentThread().isInterrupted()  ,            , 
         *             ,                ,      、   。
         */
        while (!Thread.currentThread().isInterrupted()&& more work to do) {
            do more work 
        }
    } catch (InterruptedException e) {
        //   wait sleep      
    } finally {
        //            
    }
}
ここでは、InterruptedExceptionの原則をできるだけ下位コードでキャプチャしないことに注意してください.この異常がキャプチャされると、スレッドの割り込みフラグビットがセットされます.下位コードでは、どのように対応すべきか分からないInterruptedExceptionの異常が投げ出される場合、次の2つの選択肢があります.(1)InterruptedException異常をキャプチャすると、割り込みフラグ位置ビットを、チェック割り込みフラグビットに基づいて外層コードが割り込むか否かを判断させる.
public subTask(){
    try{
        //...the works may throw a InterruptedException
    }catch(InterruptedException e){
        Thread.currentThread.interrupt();
    }
}
(2)より推奨される方法は,下位コードがInterruptedExceptionをキャプチャせず,直接外層コードに投げつけて解決することである.
public subTask() throws InterruptedException
{
    //...
}
中断が失効した場合の臨界領域:
臨界領域に入るコードは割り込みが許されないことはよく理解できるが、臨界領域は並列問題で臨界資源の反発アクセスを保護するためにわざわざロックされており、一旦割り込むことができるとロックの存在は何の意味もない.特に、デッドロックを形成したスレッドはそれぞれブロック状態にあるが、それらは割り込まれない.synに入ることがよくあるchronizedブロックのコードおよびLock.lock()の後にロックが得られずにブロックされた状態.通常、Lock.lockInterruptibly()の代わりにLock.lockInterruptibly()を使用することができる.Lock.lockInterruptibly()割り込みを受け入れることができます.この割り込みとは、ロックが得られなかった以上、ブロックするよりも他のことをしたほうがいいということです.いったん臨界領域に入ると、どんな方法でも中断してはいけません.
割り込みが無効になった場合の割り込み不可I/O:
Java 1.4以降、大量のデータに対するI/O共通チャネル(channels)メカニズムは、割り込み可能なI/Oである.つまり、このようなI/Oが割り込み状態にある場合、割り込みを使用して割り込みを解除することができる.しかし、サーバSocket.accept()、inputSteam.read()などのinterrupt()を呼び出す割り込み不可能な操作が存在するこれらの問題については、割り込み異常を放出しないため無効です.リソースが入手できない場合は、無期限にブロックされます.inputStreamなどのリソースについては、close()メソッドでリソースを閉じることができるものもあり、対応するブロックも解放されます.大きなI/Oの処理には、Channelsが推奨されます.