スレッドの中断
stop()メソッドのスレッドの不安定性
Thread.stop()メソッドは実行中のスレッドを停止できますが、このメソッドは廃止されました。
まず、次のコードを見てください。
package com.morris.concurrent.thread.interrupt;
import java.util.concurrent.TimeUnit;
/**
* 演示stop()方法带来的线程安全问题
*/
public class StopDemo {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println("thread->" + Thread.currentThread().getName() + " acquire lock.");
TimeUnit.SECONDS.sleep(3);
System.out.println("thread->" + Thread.currentThread().getName() + " 等待3s");
System.out.println("thread->" + Thread.currentThread().getName() + " release lock.");
}
} catch (Throwable e) {
// 抛出ThreadDeath Error
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("thread->" + Thread.currentThread().getName() + " acquire lock.");
}
});
t1.start();
// 休眠100ms,等待t1线程运行
TimeUnit.MILLISECONDS.sleep(100);
t2.start();
t1.stop();
}
}
結果は次のとおりです。
thread->Thread-0 acquire lock.
thread->Thread-1 acquire lock.
java.lang.ThreadDeath
at java.lang.Thread.stop(Thread.java:853)
at com.morris.concurrent.thread.interrupt.StopDemo.main(StopDemo.java:41)
実行結果から、t1.stop()メソッドを呼び出した後、t1スレッドがThreadDeathエラーをスローし、t1スレッドが保持していたロックを解放することがわかります。
一般に、ロック用のコードブロックは、データの一貫性を保護するためのものです。thread.stop()の呼び出しにより、スレッドが保持しているすべてのロックが突然(制御不能に)解放される場合、保護されたデータは不整合が発生している可能性があり、破損したデータを使用すると、他のスレッドが奇妙なアプリケーションエラーを引き起こす可能性があります。
これはkill -9
、スレッドを強制終了するコマンドの使用が推奨されない理由でもあります。これにより、スレッドの安全性に関するいくつかの難しい問題が発生する可能性があります。
interrupt()割り込みスレッド
スレッドセーフの問題に加えて、stop()メソッドは終了したスレッドの必須の終了です。Javaでは、スレッド間の一種のコラボレーションが推奨されています。
各スレッドには、現在のスレッドが中断されているかどうかを示すブール型フラグがあります。interrupt()メソッドは、スレッドの割り込みフラグビットをtrueに設定するために使用され、割り込みを受けたスレッドは、割り込みを処理するか(データ保護などの処理後に直接終了するか終了するか)を自ら決定できます。
Thread.currentThread()。isInterrupted()またはThread.interrupted()を呼び出して、スレッドの割り込みフラグが設定されているかどうかを検出できます。
これら2つの方法の違いは次のとおりです。
- Thread.currentThread()。isInterrupted():オブジェクトメソッド。割り込みフラグビットはクリアされません。
- Thread.interrupted():静的メソッド。割り込みフラグビットをクリアします。
したがって、スレッドのinterrupt()メソッドを呼び出しても、実行中のスレッドは中断されません。このメカニズムは、スレッド割り込みフラグビットを設定するだけです。割り込みフラグビットがtrueに設定されていても、プログラムでスレッド割り込みフラグビットをチェックしない場合、スレッドは通常どおり実行されます。
一般的に、割り込みスレッドは3つの状況に分けられます。
- 非ブロッキングスレッドを中断する
- 割り込みブロッキングスレッド
- 割り込み不可能なスレッドを中断する
非ブロッキングスレッドを中断する
スレッドシェア変数
この方法は比較的単純で実行可能です。変更後すぐに他のスレッドが表示されるようにするには、共有変数をvolatileに設定する必要があることに注意してください。
package com.morris.concurrent.thread.interrupt;
/**
* 中断非阻塞的线程:使用共享变量
*/
public class InterruptNonBlockWithShareVariable extends Thread {
// 设置线程共享变量
private volatile boolean isStop = false;
@Override
public void run() {
while(!isStop) {
long beginTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " is running");
// 当前线程每隔一秒钟检测一次线程共享变量是否得到通知
while (System.currentTimeMillis() - beginTime < 1000) {
}
}
if (isStop) {
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
}
public static void main(String[] args) throws InterruptedException {
InterruptNonBlockWithShareVariable thread = new InterruptNonBlockWithShareVariable();
thread.start();
Thread.sleep(5000);
thread.isStop = true;
}
}
割り込みメカニズムを使用する
package com.morris.concurrent.thread.interrupt;
/**
* 中断非阻塞的线程:使用中断机制
*/
public class InterruptNonBlockWithInterrupt extends Thread {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
long beginTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " is running");
// 当前线程每隔一秒钟检测一次线程共享变量是否得到通知
while (System.currentTimeMillis() - beginTime < 1000) {
}
}
System.out.println(Thread.currentThread().getName() + " interrupted flag:" + Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
public static void main(String[] args) throws InterruptedException {
InterruptNonBlockWithInterrupt thread = new InterruptNonBlockWithInterrupt();
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
割り込みブロッキングスレッド
スレッドがThread.sleep()、Thread.join()、Object.wait()、LockSupport.park()を呼び出すか、ブロッキングI / O操作メソッドを呼び出すと、現在のスレッドがブロッキング状態になります。
スレッドがブロック状態にある場合、interrupted()メソッドが呼び出されてスレッド割り込みフラグビットが設定されると、InterruptedExceptionがスローされ、スレッド割り込みフラグビットがクリア(falseに設定)されるため、スレッドはブロック状態を終了できます。
スレッドが中断された後、blockingメソッドが呼び出されると、直接例外がスローされます。
シェア変数を使用する
package com.morris.concurrent.thread.interrupt;
/**
* 中断阻塞的线程:使用共享变量
* 缺点:使用共享变量无法及时中断阻塞线程,因为线程不能第一时间接到中断通知,需等线程被唤醒时才知道。
*/
public class InterruptBlockThreadWithVariable extends Thread {
// 设置线程共享变量
private volatile boolean isStop = false;
@Override
public void run() {
while(!isStop) {
System.out.println(Thread.currentThread().getName() + " is running");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
public static void main(String[] args) throws InterruptedException {
InterruptBlockThreadWithVariable thread = new InterruptBlockThreadWithVariable();
thread.start();
Thread.sleep(1000);
thread.isStop = true;
}
}
短所:共有変数を使用しても、ブロックされたスレッドをすぐに中断することはできません。これは、スレッドに最初に中断を通知できず、スレッドが起こされたときにそれを知る必要があるためです。
割り込みメカニズムを使用する
package com.morris.concurrent.thread.interrupt;
/**
* 中断阻塞的线程:使用中断机制
* 注意抛出异常会清除中断标志位
*/
public class InterruptBlockThreadWithInterrupt extends Thread {
@Override
public void run() {
// 这里调用的是非清除中断标志位的isInterrupted方法
while(!Thread.currentThread().interrupted()) {
System.out.println(Thread.currentThread().getName() + " is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 由于调用sleep()方法清除状态标志位 所以这里需要再次重置中断标志位 否则线程会继续运行下去
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
public static void main(String[] args) throws InterruptedException {
InterruptBlockThreadWithInterrupt thread = new InterruptBlockThreadWithInterrupt();
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
無停止スレッドを停止するブルートフォース
package com.morris.concurrent.thread.interrupt;
import java.util.concurrent.TimeUnit;
/**
* 暴力停止不可中断的线程,如正在获取synchronized锁的线程
*/
public class ViolenceStopNotInterruptThread {
public static void main(String[] args) throws InterruptedException {
ThreadService threadService = new ThreadService();
threadService.execute(() -> {
while (true) {
System.out.println("I am working...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 等待threadService里面的线程充分运行
TimeUnit.MILLISECONDS.sleep(1000);
threadService.shutdown(1_000);
}
private static class ThreadService {
// 这个线程负责启动一个守护线程,当这个线程结束时,它创建的线程也会随之结束
private Thread executeThread;
// 用于标记任务是否结束
private boolean isStop = false;
/**
* 执行任务
* @param task
*/
public void execute(Runnable task) {
executeThread = new Thread(() -> {
// t是真正执行任务的线程
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
try {
// 这里要join,不然守护线程还没开始运行就可能结束了,另外也便于超时结束
executeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
isStop = true;
});
executeThread.start();
}
/**
* 等待mills还未结束就终止线程
* @param mills
*/
public void shutdown(long mills) {
long endMills = System.currentTimeMillis() + mills;
long remainMills = mills;
while (remainMills > 0 && !isStop) {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
remainMills = endMills - System.currentTimeMillis();
}
// 中断线程,executeThread结束后,其创建的守护线程也会随之结束
executeThread.interrupt();
}
}
}