如何正确的停止线程?
- 一个线程一般都是正常的运行直到执行任务完毕而结束。那什么时候我们需要对线程进行停止的动作呢?例如用户行为的一个取消动作,运行超时或者运行过程中出现错误也是需要停止当前线程,应用程序需要进行重启等都需要我们主动的停止正在运行的线程,如何安全可靠的停止线程并不是一件简单的事情。
- java语言并没有一种机制可以安全可靠的去停止一个线程,但是提供了一个中断(interrupt)机制,是通过启动一个线程去通知当前真正运行的线程,告诉它你别运行了,可以停止了。而不是强者进行停止。
- 我们都知道interrupt是中断的意思,最终的决定权还是在被通知的线程,看它心情,可以停止也可以不停止。如果它不停止我们也无可奈何? 是不是拽!
- 看到这里,肯定有很多人疑惑,我们是程序的开发者,为什么会作为停止线程的机制呢?我们看一下下图的场景和解释就很清晰了。
- java语言设计将线程停止的权利和步骤交给了被停止的线程本身。被停止的线程更加清楚具体什么时机进行停止。
使用interrupt进行通知而不是强制停止
interrupt的正确使用方法
普通情况下
- 如下代码所示:
public class MyNormalInterruptDemo {
public static void main(String[] args) {
Thread thread = new Thread(new MyNormalInterrupt());
thread.start();
//执行完进行中断处理
thread.interrupt();
}
}
class MyNormalInterrupt implements Runnable {
@Override
public void run() {
System.out.println("线程开始执行了");
int i = 0;
while (i < Integer.MAX_VALUE){
i++;
}
System.out.println("最终执行的结果为:"+i);
}
}
- 控制台输出
线程开始执行了
最终执行的结果为:2147483647
- 从结果上看最终程序还是正常执行了,虽然我们在调用端执行了interrupt中断操作,但是我们在线程执行体中并没有对中断操作做处理,即使调用端发出了中断信号,我们线程执行体并不理会它。接下来看一下如果在线程体内理会中断操作。
public class MyNormalInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyNormalInterrupt());
thread.start();
//执行完进行中断处理
Thread.sleep(1000);
thread.interrupt();
}
}
class MyNormalInterrupt implements Runnable {
@Override
public void run() {
System.out.println("线程开始执行了");
int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("最终执行的结果为:"+i);
}
}
- 控制台
线程开始执行了
最终执行的结果为:1103717170
- 显然程序没有执行完毕,因为最终的执行结果为2147483647,在由于在循环条件中添加了一个线程是否被中断的条件,如何线程中断了,循环结束线程执行完毕。
线程阻塞
- 代码如下:
public class MySleepInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MySleepInterrupt());
thread.start();
//执行完进行中断处理
Thread.sleep(100);
thread.interrupt();
}
}
class MySleepInterrupt implements Runnable {
@Override
public void run() {
System.out.println("线程开始执行了");
try {
int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
i++;
}
Thread.sleep(1000L);
System.out.println("最终执行的结果为:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 控制台
线程开始执行了
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.karl.concurrent.chapter07.MySleepInterrupt.run(MySleepInterruptDemo.java:28)
at java.lang.Thread.run(Thread.java:748)
- 在run方法内执行sleep方法时就会提示获取InterruptedException异常
- 当线程正在休眠的过程中收到了中断信号,响应的方式就是抛出InterruptedException异常。
线程迭代阻塞
- 代码如下所示:
public class MyEveryLoopSleepInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyEveryLoopSleepInterrupt());
thread.start();
//执行完进行中断处理
Thread.sleep(100);
thread.interrupt();
}
}
class MyEveryLoopSleepInterrupt implements Runnable {
@Override
public void run() {
System.out.println("线程开始执行了");
try {
int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
i++;
Thread.sleep(10L);
}
System.out.println("最终执行的结果为:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 控制台
线程开始执行了
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.karl.concurrent.chapter07.MyEveryLoopSleepInterrupt.run(MyEveryLoopSleepInterruptDemo.java:29)
at java.lang.Thread.run(Thread.java:748)
- 观察一下上述代码和线程阻塞的区别
- 阻塞在循环体内
- 在循环条件内不进行线程是否被中断的判断
- 原因: 因为在线程的执行体内,大部分的时候线程在休眠状态,休眠状态的线程被中断会抛出InterruptedException异常,这时候循环条件的判断其实就多余了。所以不需要每次循环的过程中都检查是否被中断。因为在循环体内sleep会帮我们响应这个中断。
interrupt的错误使用方法
- 如果在while里面使用try catch 会导致中断失效。代码如下所示:
public class MyErrorInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyErrorInterrup());
thread.start();
//执行完进行中断处理
Thread.sleep(100);
thread.interrupt();
}
}
class MyErrorInterrup implements Runnable {
@Override
public void run() {
System.out.println("线程开始执行了");
int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
i++;
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("最终执行的结果为:" + i);
}
}
- 控制台
- 为什么线程一直在执行?
- 很多小伙伴很快就想到了因为循环体内捕获了异常,没有抛出去所以一直在执行。
- 那为什么我们在循环条件加了是否被中断这个判断线程还在执行呢?面对第二个疑问小伙伴们可能就无法回答了。
- 原因如下所示:
- 因为java在设置Sleep方法机制的时候,一旦响应了中断,就将线程的中断标志清楚,因为当我们响应中断后,在继续根据线程的中断标记位作为判断条件其实已经失效了。看到这里肯定有不少的小伙伴恍然大悟的哦了一声。
实际工作开发中的最近实践
- 上述一些简单的案例为了帮助我们更好的理解中断的原理,下面我们将讲述在我们实际的开发过程中如何正确的中断正在执行的线程。
第一种: 传递中断(推荐)
- 先看一下错误示范
public class RightWayStopThreadInProd {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRightWayStopThreadInProd());
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}
class MyRightWayStopThreadInProd implements Runnable{
@Override
public void run() {
System.out.println("开始执行业务");
while (true){
// 模拟在执行的过程中调用其他方法
this.method();
}
}
private void method(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
控制台
-
上述的代码主要的问题我想大家也应该清楚了,在调用method()方法的过程中,由于终端响应被try/catch了run方法是无法感知的。所以线程在一直运行。
-
所以主要的问题在method方法,不能将中断响应给自己吐了导致run方法感知不到,即使method方法自己无法解决中断异常,正确的做法应该上报,在高层(调用method方法)自行根据情况处理而不是私下解决。
-
正确的代码如下所示:
public class RightWayStopThreadInProd {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRightWayStopThreadInProd());
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}
class MyRightWayStopThreadInProd implements Runnable{
@Override
public void run() {
System.out.println("开始执行业务");
while (true){
// 模拟在执行的过程中调用其他方法
try {
this.method();
} catch (InterruptedException e) {
//因为在run方法中不能在往上进行抛错,在这里我们能做的就是保存日志或者停止程序运行
System.out.println("保存日志/停止程序运行");
e.printStackTrace();
}
}
}
private void method() throws InterruptedException {
Thread.sleep(1000);
}
}
恢复中断
- 当我们不想传递中断或者无法中断的时候我们可以进行恢复中断处理。
- 在catch语句中在调用Thread.currentThread().interrupt()进行恢复中断,以便在后续的执行中检查是否发生了中断。就是上述描述了自己吐了中断在吐出来的意思。
- 具体代码如下所示
public class RightWayStopThreadInProd2 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRightWayStopThreadInProd());
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
class MyRightWayStopThreadInProd2 implements Runnable {
@Override
public void run() {
System.out.println("开始执行业务");
while (true) {
if(Thread.currentThread().isInterrupted()){
System.out.println("发生了中断,退出执行");
break;
}
// 模拟在执行的过程中调用其他方法
this.method();
}
}
private void method() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
- 控制台
开始执行业务
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.karl.concurrent.chapter07.MyRightWayStopThreadInProd.method(RightWayStopThreadInProd.java:35)
at com.karl.concurrent.chapter07.MyRightWayStopThreadInProd.run(RightWayStopThreadInProd.java:29)
at java.lang.Thread.run(Thread.java:748)
发生了中断,退出执行
- 这个时候需要我们自行在上层每次循环进行判断是否发生了中断。
响应中断的其他方法
- 上述我们描述线程阻塞的响应中断,类似的还有其他方法如下所示:
- Object:wait()
- Thread:join()
- BlockingQueue:take()/put()
- Lock:lockInterruptibly()
- CountDownLatch:await()
- CyclicBarrier:await()
- 等…
总结
-
看到这里,相信大家对线程中断有了一定的认知。本人能力和表达有限,有不足的地方望及时指正。
-
如果文章有帮助到你欢迎关注微信公众号《后端学长》