スレッドを終了する方法を尋ねると、おそらくほとんどの人は Thread.stop メソッドを呼び出すことができることを知っています。
しかし、この方法は jdk1.2 以降は推奨されなくなりました。なぜ推奨されないのでしょうか?
最初にこのメソッドの定義を見てみましょう:
@Deprecated(since="1.2") public final void stop() { @SuppressWarnings("removal") SecurityManager security = System.getSecurityManager(); if (security != null) { checkAccess(); if (this != Thread.currentThread()) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); } } // A zero status value corresponds to "NEW", it can't change to // not-NEW because we hold the lock. if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(new ThreadDeath()); }
コードからわかるように、stop メソッドは最初にスレッドのアクセス許可があるかどうかを検出します。権限がある場合は、現在のスレッドが新しく作成されたスレッドかどうかを確認し、権限がない場合は、resume メソッドを呼び出してスレッドのサスペンド状態を解除します。
最後に stop0 メソッドを呼び出してスレッドを終了します。
Resume と stop0 は 2 つのネイティブ メソッドであり、具体的な実装についてはここでは説明しません。
停止方法は非常に合理的で問題ないようです。では、なぜこの方法は安全ではないのでしょうか?
次に例を見てみましょう。
NumberCounter クラスを作成します。このクラスには、数値に 1 を加算するために使用される安全なメソッド増加番号があります:
public class NumberCounter { //要保存的数字 private volatile int number=0; //数字计数器的逻辑是否完整 private volatile boolean flag = false; public synchronized int increaseNumber() throws InterruptedException { if(flag){ //逻辑不完整 throw new RuntimeException("逻辑不完整,数字计数器未执行完毕"); } //开始执行逻辑 flag = true; //do something Thread.sleep(5000); number++; //执行完毕 flag=false; return number; } }
実際、実際の作業では、このようなメソッドは次のことを行う必要があるかもしれません。比較の実行 時間がかかるため、ここでは Thread.sleep を呼び出して、この時間のかかる操作をシミュレートします。
ここには、increaseNumber メソッドが正常に実行されたかどうかをマークするフラグ パラメーターもあります。
わかりました。次に、スレッドでこのクラスのメソッドを呼び出して、何が起こるかを確認します。
public static void main(String[] args) throws InterruptedException { NumberCounter numberCounter= new NumberCounter(); Thread thread = new Thread(()->{ while (true){ try { numberCounter.increaseNumber(); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(3000); thread.stop(); numberCounter.increaseNumber(); }
ここでは、スレッドを作成し、このスレッドが実行されるまで 3 秒間待ちます。その後、 thread.stop メソッドが直接呼び出され、次の例外が見つかりました:
Exception in thread "main" java.lang.RuntimeException: ロジックが不完全で、デジタル カウンターが実行されていません
com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
com.flydean.Main.main(Main.java:18)
これはスレッドが原因です.stop メソッド スレッドの実行が直接終了されるため、mberCounter.increaseNumber は完了しません。
ただし、この未完了の状態は隠蔽されているため、thread.stop メソッドを使用してスレッドを終了すると、不明な結果が生じる可能性があります。
したがって、thread.stop は安全ではないと言えます。
では、thread.stop メソッドが呼び出されない場合、スレッドを安全に終了するにはどうすればよいでしょうか?
いわゆる安全性とは、スレッド内のロジックが半分実行されるのではなく、完全に実行される必要があることを意味します。
この効果を実現するために、Thread は、interrupt、interrupted、isInterrupted という 3 つの同様のメソッドを提供します。
interrupt はスレッドの割り込みフラグを設定します。interrupted は割り込みを検出して割り込みステータスをクリアします。isInterrupted は割り込みを検出するだけです。もう 1 つの重要な点は、interrupted は現在のスレッドで動作するクラス メソッドであるということです。interrupt と isInterrupted はこのスレッド、つまり、コード内でこのメソッドを呼び出すインスタンスによって表されるスレッドで動作します。
interrupt は割り込みメソッドです。そのワークフローは次のとおりです:
現在のスレッド インスタンスが wait()、wait(long)、または wait(long, int) メソッド、join()、join(long)、join(long, int) メソッド、または Thread.sleep(long) または Thread.sleep(long, int) メソッドがこのインスタンスで呼び出され、ブロック状態になっています。中断ステータスはクリアされ、InterruptedException が受信されます。
InterruptibleChannel での I/O 操作中にこのスレッドがブロックされた場合、チャネルは閉じられ、スレッドの割り込みステータスが true に設定され、スレッド A java.nio .channels.ClosedByInterruptException 例外が受信されます。
このスレッドが java.nio.channels.Selector でブロックされている場合、スレッドの割り込みステータスは true に設定され、選択操作からすぐに戻ります。
上記の条件がいずれも当てはまらない場合は、割り込みステータスを true に設定します。
上記の例では、NumberCounter の増加数値メソッドで Thread.sleep メソッドを呼び出しているため、この時点でスレッドの割り込みメソッドが呼び出されると、スレッドは An をスローします。中断された例外。
上記の呼び出し例を次のように変更します:
public static void main(String[] args) throws InterruptedException { NumberCounter numberCounter = new NumberCounter(); Thread thread = new Thread(() -> { while (true) { try { numberCounter.increaseNumber(); } catch (InterruptedException e) { System.out.println("捕获InterruptedException"); throw new RuntimeException(e); } } }); thread.start(); Thread.sleep(500); thread.interrupt(); numberCounter.increaseNumber(); }
実行後に再試行します:
Exception in thread "main" Exception in thread "Thread-0" java.lang.RuntimeException: 逻辑不完整,数字计数器未执行完毕
at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
at com.flydean.Main2.main(Main2.java:21)
java.lang.RuntimeException: java.lang.thread.interrupt: sleep interrupted
at com.flydean.Main2.lambda$main$0(Main2.java:13)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:17)
at com.flydean.Main2.lambda$main$0(Main2.java:10)
... 1 more
捕获InterruptedException
可以看到,我们捕获到了这个InterruptedException,并且得知具体的原因是sleep interrupted。
从上面的分析可以得知,thread.stop跟thread.interrupt的表现机制是不一样的。thread.stop属于悄悄终止,我们程序不知道,所以会导致数据不一致,从而产生一些未知的异常。
而thread.interrupt会显示的抛出InterruptedException,当我们捕捉到这个异常的时候,我们就知道线程里面的逻辑在执行的过程中受到了外部作用的干扰,那么我们就可以执行一些数据恢复或者数据校验的动作。
在上面的代码中,我们是捕获到了这个异常,打印出异常日志,然后向上抛出一个RuntimeException。
正常情况下我们是需要在捕获异常之后,进行一些处理。
那么自己处理完这个异常之后,是不是就完美了呢?
答案是否定的。
因为如果我们自己处理了这个InterruptedException, 那么程序中其他部分如果有依赖这个InterruptedException的话,就可能会出现数据不一致的情况。
所以我们在自己处理完InterruptedException之后,还需要再次抛出这个异常。
怎么抛出InterruptedException异常呢?
有两种方式,第一种就是在调用Thread.interrupted()清除了中断标志之后立即抛出:
if (Thread.interrupted()) // Clears interrupted status! throw new InterruptedException();
还有一种方式就是,在捕获异常之后,调用Thread.currentThread().interrupt()再次中断线程。
public void run () { try { while (true) { // do stuff } }catch (InterruptedException e) { LOGGER.log(Level.WARN, "Interrupted!", e); // Restore interrupted state... Thread.currentThread().interrupt(); } }
这两种方式都能达到预想的效果。
以上がJavaでスレッドを終了する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。