Java如何停止一个线程
Posted 风在哪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java如何停止一个线程相关的知识,希望对你有一定的参考价值。
Java如何停止一个线程
在Java中,停止一个正在运行的线程,我们可以通过如下方法实现:
- 设置一个volatile类型的变量,通过判断变量的值来确定run方法是否运行完成正常退出
- 通过interrupt()方法来通知,给线程打上一个终止的标志,但是具体的停止逻辑需要在线程内部实现
此外还有一种方法是调用stop()方法来停止线程,但是这个方法不安全:它会释放该线程持有的所有锁,一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。suspend方法:容易发生死锁,虽然调用suspend方法目标线程会停下来,但是仍然持有之前的锁,其他线程都不能访问锁定的资源,就会造成死锁,除非被挂起的线程恢复运行。
所以这里只提及了上述的两种方法。
volatile变量
我们可以自己实现的线程中设置了volatile的变量,然后在线程内部判断volatile是否满足运行的条件,满足就继续运行,不满足就退出线程。我们可以在调用线程的地方设置这个volatile变量,通过对volatile的设置达到停止线程的目的。
这里为什么使用volatile变量呢,因为volatile变量会保证多线程之间的可见性,变量的值一经修改所有的线程都能看到这个改变,根据这一性质才能停止线程的运行。
一个简单的例子:
public class ThreadInterrupt extends Thread{
private volatile boolean exit = false;
@Override
public void run() {
while (!exit) {
System.out.println("Running!");
}
}
public static void main(String[] args) throws Exception {
ThreadInterrupt thread = new ThreadInterrupt();
thread.start();
sleep(10);
thread.exit = true;
thread.join();
System.out.println("线程已退出!");
}
}
但是,使用volatile变量停止线程还是不够全面,某些情况下索然可用,但是某些情况下可能会有严重的问题。
Java并发编程实战指出了其缺陷:如果我们遇到了线程长时间阻塞(例如生产者消费者中就存在这样的情况),就没办法及时唤醒它,或者永远都无法唤醒该线程。此时我们就可以使用interrupt了,interrupt在设计之初就把wait等长阻塞作为一种特殊情况考虑在内,所以可以在线程内部有sleep或者wait方法时使用interrupt方法停止线程。
interrupt()方法
我们首先看看线程中断的几个方法:
- java.lang.Thread#interrupt():中断目标线程,给目标线程发出一个中断信号,线程被打上中断标记
- java.lang.Thread#isInterrupted():判断目标线程是否被中断,不会清除中断标记
- java.lang.Thread#interrupted():判断目标线程是否被中断,会清除中断标记
从上图源码可以发现,interrupted()方法和isInterrupted()方法都调用了本地isInterrupted()方法,该方法根据传入的参数决定是否清除中断状态,如果ClearInterrupted为true的话就要清除线程的中断标志,如果为false则不会清除。
使用interrupt暂停线程又有两种场景,第一种场景是线程的run方法中没有sleep和wait等会响应中断的方法,第二种场景就是有sleep和wait方法。我们首先来看看run方法中不包含sleep和wait方法的场景。
run方法不包含sleep和wait等响应中断的方法
我们首先看一个错误的示范。
首先我们看看打印Integ.MAX_VALUE/10以内的1000000倍数:
public static void main(String[] args) throws Exception {
int i = 0;
while (i < Integer.MAX_VALUE / 10) {
if (i % 1000000 == 0) {
System.out.println(i + "是1000000的倍数");
}
i++;
}
}
它的结果如下所示:
再来看看错误的使用interrupt方法的例子:
public class Interrupted implements Runnable {
@Override
public void run() {
int i = 0;
while (i < Integer.MAX_VALUE / 10) {
if (i % 1000000 == 0) {
System.out.println(i + "是1000000的倍数");
}
i++;
}
System.out.println("任务成功执行完毕");
}
public static void main(String[] args) {
Thread thread = new Thread(new Interrupted());
// 开启线程
thread.start();
// 中断子线程
thread.interrupt();
}
}
可以看到它的运行效果,其实和正常运行是一样的,也就是说线程没有被终止。
那么正确的方法应该是什么呢,我们只需要改动run方法即可,在run方法中判断线程终止的状态,如果是终止的话就退出run方法。前面说过interrupt方法只是打了一个标志,标志线程被中断了,那么我们可以根据这个标志判断是否需要停止线程,代码如下:
public class Interrupted implements Runnable {
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted() && i < Integer.MAX_VALUE / 10) {
if (i % 1000000 == 0) {
System.out.println(i + "是1000000的倍数");
}
i++;
}
System.out.println("任务成功执行完毕");
}
public static void main(String[] args) {
Thread thread = new Thread(new Interrupted());
// 开启线程
thread.start();
// 给子线程增加运行时间,不加这句代码线程会直接被终止
Thread.sleep(500);
// 中断子线程
thread.interrupt();
}
}
运行结果如下:
可以看到线程成功被终止了!
run方法包含sleep和wait等响应中断的方法
当我们调用响应中断的方法时,如果此时调用了interrupt方法,响应中断的方法就会抛出异常,那么我们捕获并处理这个异常就能使线程正常的退出了,例如:
public class Interrupted implements Runnable {
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted() && i < 1000) {
if (i % 200 == 0) {
System.out.println(i + "是200的倍数");
}
i++;
}
try {
// sleep方法响应中断,会抛出InterruptedException异常
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务成功执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Interrupted());
// 开启线程
thread.start();
// 给子线程增加运行时间,等待while执行完毕
Thread.sleep(50);
// 中断子线程
thread.interrupt();
}
}
其运行结果如下:
可以看到我们的while循环完成之后,在sleep进行了阻塞,此时进行中断的话,sleep响应中断,抛出InterruptedException异常,并打印出异常堆栈。
但是如果我们将sleep()放在while循环内部,这个方法还会成功吗?例如:
public class Interrupted implements Runnable {
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted() && i < 1000) {
if (i % 10 == 0) {
System.out.println(i + "是10的倍数");
}
i++;
try {
// sleep方法响应中断,会抛出InterruptedException异常
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("任务成功执行完毕");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Interrupted());
// 开启线程
thread.start();
// 给子线程增加运行时间
Thread.sleep(50);
// 中断子线程
thread.interrupt();
}
}
其运行结果如下:
我们的预期是,当线程进入while循环时,会休眠500毫秒,当50毫秒时主线程发起了中断信号,sleep响应中断,打印异常堆栈,下次再进入while循环时,因为线程被设置为中断状态,所以会直接退出。
但是实际效果却不是这样,再次进入while循环时,还会继续执行相应的逻辑。这是为什么呢?
我们可以看看sleep的注释:
通过其注释我们可以发现,当sleep相应中断并抛出异常后,会清除线程的中断状态。所以这也就是为什么我们在while循环中发生上述的情况。
所以对于中断,我们需要传递中断,并对中断进行处理,或者处理响应中断方法抛出的异常。
如果无法传递中断就恢复中断。就像上面sleep在while循环中会清除中断一样,我们可以在catch中再恢复中断。例如:
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().interruptisInterrupted() && i < 1000) {
if (i % 10 == 0) {
System.out.println(i + "是10的倍数");
}
i++;
try {
// sleep方法响应中断,会抛出InterruptedException异常
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
// 尝试恢复中断
Thread.currentThread().interrupt();
}
}
System.out.println("任务成功执行完毕");
}
总结
我们在实际使用中,如果要停止一个线程的话,尽量使用interrupt()方法,尽量减少volatile变量的使用。
参考
以上是关于Java如何停止一个线程的主要内容,如果未能解决你的问题,请参考以下文章