如何中止Java中的线程
如何正确的中止一个运行中的线程
Java作为第一款官方声明支持多线程的编程语言,其早期提供的一些Api并不是特别的完善,所以可以看到Thread类中的一些早期方法都已经被标记上过时了,例如stop、resume,suspend,destory方法都被标记上过时的标签。那为了弥补这些缺失的功能,后续的Java提供了一些额外的方法,例如interrupt这样的方法。
stop方法会立即中止线程的运行并抛出错误,同时释放所有的锁,这可能产生数据安全问题(https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html)。interrupt方法的实现方式更类似于给线程发送了一个信号(简单的理解为给线程设置了一个属性来标记是否应当停止当前线程,默认为false),线程接收到信号之后具体如何处理,是中止线程还是正常运行取决于运行的线程的具体逻辑。
中止线程的方法大致分两种
- 通过Thread.interrupt()
- 通过线程间的共享标记变量
通过interrupt()来中止一个线程
由于被中止的线程可能处于不同的状态,被终止的线程需要自行检测是否有人发信号给他,通知应该停止工作。
检测是否有人发信号的方法有两种
- Thread.currentThread().isInterrupted() 返回当前线程上的标识位。推荐使用,该方法调用后不会修改标识;
- Thread.interrupted() 返回当前线程上的标识位,同时调用后会立即把标识位修改位false。
终止一个runnable线程的最佳实践:
while(!Thread.currentThread().isInterrupted() && more work to do){
do more work
}
处于runnable状态的线程被interrupt
看一个例子,对于一个循环打印running的线程,我们在main线程中调用了interrupt()方法,但是线程并没有按照我们预想的那样停下来,这样的代码并不是按照最佳实践的方式来运行的,我们没有把线程是否有接受到interrupt的信号放到while的判断去。
public class InterruptRunning {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("running");
}
});
thread.start();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("Test End");
}
}
//输出结果
//一直持续不断的输出running ,并没有像期待的那样2ms之后输出Test End
以上的代码的正确写法应该是,可以看到结果是我们期待的
public class InterruptRunning {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("running");
}
});
thread.start();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("Test End");
}
}
//输出结果
running
running
running
running
running
running
running
running
Test End
处于blocked/waiting/timed waiting状态的线程被interrupt
如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
即先后做了三件事
- 使线程状态由阻塞状态变为runnable
- 抛出InterruptedException异常
- 如果InterruptedException异常被捕获,会立即将中断标识由true设置为false
举个例子,我们在循环的时候通过中断标识位来决定是否循环,并且将try catch代码块置于while循环内部。此时中断产生的异常会先被try catch捕获,然后中断标识位会被设置位false;while循环不会中止,线程依旧执行。
在main线程中调用standardInterrupt.interrupt();尝试中止中止StandardInterrupt线程,线程的输出内容说明了其捕获到了异常,但是检查标识发现值位false说明已经被重置了,于是可以看到while循环条件满足,线程不会停止,依旧运行下去。
public class StandardInterrupt extends Thread{
public static void main(String[] args) throws InterruptedException {
StandardInterrupt standardInterrupt = new StandardInterrupt();
System.out.println("main thread --> start running");
standardInterrupt.start();
Thread.sleep(3000);
System.out.println("main thread --> send signal");
standardInterrupt.interrupt();
Thread.sleep(3000);
System.out.println("main thread --> stop application");
}
public void run(){
while(!Thread.currentThread().isInterrupted()){
System.out.println("active thread --> i am working");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
System.out.println("active thread --> detective interrupt");
System.out.println("active thread --> check the signal: " +Thread.currentThread().isInterrupted());
}
}
System.out.println("active thread --> finish my working");
}
}
//输出
main thread --> start running
active thread --> i am working
active thread --> i am working
active thread --> i am working
main thread --> send signal
active thread --> detective interrupt
active thread --> check the signal: false
main thread --> stop application
active thread --> i am working
active thread --> i am working
active thread --> i am working
......
我们再稍作修改如上的代码,在捕获异常之后,主动的再次触发一次interrupt(),这可以将被自动设置回false的中断标识位再次变为true。
这次可以看到active thread --> finish my working输出,因为while循环去检查标识的时候,Thread.currentThread().isInterrupted() = true;
public class StandardInterrupt extends Thread{
public static void main(String[] args) throws InterruptedException {
StandardInterrupt standardInterrupt = new StandardInterrupt();
System.out.println("main thread --> start running");
standardInterrupt.start();
Thread.sleep(3000);
System.out.println("main thread --> send signal");
standardInterrupt.interrupt();
Thread.sleep(3000);
System.out.println("main thread --> stop application");
}
public void run(){
while(!Thread.currentThread().isInterrupted()){
System.out.println("active thread --> i am working");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
System.out.println("active thread --> detective interrupt");
System.out.println("active thread --> check the signal: " +Thread.currentThread().isInterrupted());
Thread.currentThread().interrupt();
}
}
System.out.println("active thread --> finish my working");
}
}
//输出
main thread --> start running
active thread --> i am working
active thread --> i am working
active thread --> i am working
main thread --> send signal
active thread --> detective interrupt
active thread --> check the signal: false
active thread --> finish my working
main thread --> stop application
......
另一种修改方式,是将try catch放在while循环外面,这样跳出while的原因是因为检测到异常。然后try catch捕获异常,修改标识位。
public class StandardInterrupt extends Thread {
public static void main(String[] args) {
StandardInterrupt standardInterrupt = new StandardInterrupt();
System.out.println("main thread --> start running");
standardInterrupt.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread --> send signal");
standardInterrupt.interrupt();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread --> stop application");
}
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("active thread --> i am working");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("active thread --> detective interrupt");
System.out.println("active thread --> check the signal: " + Thread.currentThread().isInterrupted());
}
System.out.println("active thread --> finish my working");
}
}
//输出
main thread --> start running
active thread --> i am working
active thread --> i am working
active thread --> i am working
main thread --> send signal
active thread --> detective interrupt
active thread --> check the signal: false
active thread --> finish my working
main thread --> stop application
......
总结一下对于要处理这种blocked状态的线程应该正确书写代码的格式
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 {
//线程结束前做一些清理工作
}
}
或者
public void run() {
while (!Thread.currentThread().isInterrupted()&& more work to do) {
try {
...
sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//重新设置中断标示
}
}
}
处于blocked状态的线程
- 由synchronized,或者是由reentrantLock.lock()导致的锁是无法响应interrupt()方法的,他们只有在获取锁之后正常运行的时候才能响应。
- reentrantLock.tryLock(long timeout, TimeUnit unit),reentrantLock.lockInterruptibly()方法所导致的blocked状态却是能正确响应interrupt()方法的,锁住的线程将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。