简介
俗话说:上山容易下山难。知道如何启动线程,那么到底如何停止线程呢?本文将讲解Java中三种场景下如何正确的停止线程,分别是普通情况、堵塞状态、循环中堵塞状态,三种情况下如何正确的停止线程。
场景一:普通场景下如何停止线程
如何停止线程:
我们只能调用线程的interrupt()方法通知系统停止线程,并不能强制停止线程。线程能否停止,何时停止,取决于系统。
注意
Java中线程的
stop()
、suspend()
、resume()
三个方法都已经被弃用,所以不再使用stop()
方法停止线程。
1.代码演示
代码逻辑描述:
创建一个子线程,子线程汇总循环打印数字。然后我们在其他线程中,调用子线程的interrupt()
方法停止线程,观察停止前后的控制台输出情况,理解上述线程停止的含义。
public class StopNormalThread {
public static void main(String[] args) throws InterruptedException {
// 最好的停止线程方式:通过interrupt通知线程停止线程;而且只能通知,并不能强制让其停止。
testInterruptThread();
}
/**
* 线程只能通知停止,不能强制立刻停止测试。
*/
private static void testInterruptThread() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i <= 1000000; i++) {
// 判断如果线程没有被中断,则继续输出
if (!Thread.currentThread().isInterrupted()) {
System.out.println("当前输出位置:" + i);
}
}
}
});
thread.start();
System.out.println("子线程已经启动");
//主线程休眠,让子线程跑一会儿,然后让子线程停止
Thread.sleep(1000);
System.out.println("主线程休眠结束,开始停止子线程");
// 终止后,发现for循环还会继续输出内容,少许时间后才停止。说明我们无法控制线程立刻停止。
thread.interrupt();
System.out.println("子线程已被停止");
}
}
程序输出节选:
子线程已经启动
当前输出位置:0
当前输出位置:1
当前输出位置:2
略……
当前输出位置:430290
当前输出位置:430291
当前输出位置:430292
主线程休眠结束,开始停止子线程
当前输出位置:430293
当前输出位置:430294
当前输出位置:430295
当前输出位置:430296
当前输出位置:430297
当前输出位置:430298
当前输出位置:430299
当前输出位置:430300
当前输出位置:430301
当前输出位置:430302
当前输出位置:430303
子线程已被停止
Process finished with exit code 0
运行结果解释:
我们可以看出,子线程创建并启动后,开始输出数字,当主线程中调用thread.interrupt()
方法时,即通知系统要停止子线程的运行了,此时控制台中还是会有数字继续输出,这就表明:我们只能通过thread.interrupt()
方法通知系统停止子线程,但子线程可能不会立即停止,可能还会继续运行一段时间才会停止。
场景一:普通场景下通过调用thread.interrupt()方法停止线程
场景二:堵塞状态下如何停止线程
什么是堵塞状态:阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,即还没有真正运行Synchronized修饰的代码时的状态。BLOCKED或WAITING或TIME_WAITING这三种统称为堵塞状态
关于线程状态的内容,如不有不明白的地方,可参考:《Java线程状态完全解析教程》
代码逻辑描述:
在主线程中创建一个子线程并运行,然后休眠两秒后,让子线程停止。
在子线程中,循环打印数字,然后让子线程休眠1秒(此时子线程进入阻塞状态),然后子线程会被停止。
public class StopBlockThread {
public static void main(String[] args) throws InterruptedException {
testBlockingInterruptThread();
}
/**
* 中止堵塞状态的线程示例
*/
private static void testBlockingInterruptThread() {
try {
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i <= 1000000; i++) {
// 判断如果线程没有被中断,则继续输出
if (!Thread.currentThread().isInterrupted()) {
System.out.println("当前输出位置:" + i);
}
}
try {
// 模拟线程堵塞
System.out.println("--1--模拟线程堵塞中");
Thread.sleep(1000);
System.out.println("--2--此行不会被打印,即线程以被停止");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("让子线程运行两秒,然后再通知其停止");
Thread.sleep(2000);
/*让子线程for循环在没有循环结束时,接收到停止信号,此时子线程停止,并执行sleep(模拟阻塞状态)时,
会抛出sleep interrupted中断异常,表示堵塞状态也被中断了,即堵塞状态的线程成功被终止了 */
thread.interrupt();
System.out.println("通知停止线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序输出节选:
让子线程运行两秒,然后再通知其停止
当前输出位置:0
当前输出位置:1
当前输出位置:2
略……
当前输出位置:897419
当前输出位置:897420
当前输出位置:897421
通知停止线程
--1--模拟线程堵塞中
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at thread.stop.StopBlockThread$1.run(StopBlockThread.java:31)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
运行结果解释:
分析下子线程的运行流程,子线程启动后循环打印数字,打印语句有一个判断条件!Thread.currentThread().isInterrupted()
,意思是只要不被中断才打印,若中断了,就不再打印,转而往下执行Thread.sleep(1000)
语句,进入堵塞状态。
此时主线程调用了thread.interrupt()
,系统尝试中断子线程,发现子线程在阻塞状态中,所以会抛出异常sleep interrupted
,然后我们发现控制台输出Process finished with exit code 0
,表示线程被正常停止了。
场景二:阻塞状态下也可以通过thread.interrupt()停止线程
场景三:循环中堵塞状态下如何停止线程
代码逻辑描述:
主线程中创建一个子线程并运行,两秒钟后,停止子线程。
子线程run()
方法中,写一个循环打印数字逻辑,并在每次循环中,都调用一次Thread.sleep(20)
,目的是让每次循环都进入堵塞状态。此时要想正常停止线程,必须要在循环外部增加try-catch
语句,即当阻塞被停止时,会抛出异常,此时即可终止循环,停止线程
public class StopLoopBlockThread {
public static void main(String[] args) {
testLoopBlockStopThread();
}
/**
* 循环中存在堵塞的线程停止示例(关键是将循环放到try-catch内部才生效)
*/
private static void testLoopBlockStopThread() {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
/* 此处不需要判断线程是否已经被中断了,因为如果在循环中的休眠过程中(堵塞时),
收到interrupt信号,则会立刻抛出停止休眠异常*/
for (int i = 0; i < 1000; i++) {
System.out.println("当前输出位置:" + i);
// 模拟每次循环都堵塞
Thread.sleep(20);
}
} catch (InterruptedException e) {
/* try-catch一定放在循环外部,否则线程将不会停止。因为中断异常在循环中被捕获,
但循环并没有满足循环停止条件,所以知道循环运行结束,才会停止。即时在循环终止条件中,
添加`!Thread.currentThread().isInterrupted()`判断,循环也不会停止,因为线程的sleep()方法,
一旦抛出被中断异常后,其isInterrupted标记也会被清除,所以也无法立即停止线程*/
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
程序输出节选:
当前输出位置:0
当前输出位置:1
当前输出位置:2
当前输出位置:3
略...
当前输出位置:95
当前输出位置:96
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at thread.stop.StopLoopBlockThread$1.run(StopLoopBlockThread.java:27)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
运行结果解释:
本例最关键地方在于如果仅仅把try-catch
语句包裹子线程中的在Thread.sleep(20)上,一旦接收到终止信号,程序开始终止sleep()
方法的阻塞状态会抛出异常,此时异常将在循环体内被捕获,循环并不能终止,子线程还是会继续运行,一直运行到for循环结束才能正常停止。这样就不符合我们预期,我们想让程序尽快的做出停止操作,如果程序没有终止,则会造成很多不可挽回的结果。
场景三:循环中堵塞状态下正确的停止线程,也可以通过thread.interrupt()停止线程,但需要在子线程的循环外部增加
try-catch
代码块,捕获到中止堵塞状态异常时,也能停止线程。
总结
本文介绍了线程在三种场景下的停止方式,都是通过interrupt()
方法来停止的,但特殊的是停止循环中的阻塞线程时,需要在循环外部增加try-catch
代码块,捕获到中止堵塞状态异常时停止线程。希望这篇文章可以让你掌握如何在多线程编程中,正确的停止线程。喜欢本文请收藏、点赞、关注。
参考资料补充:
关于多线程、synchronized关键字
、wait()
、notify()
方法的系列教程,请参考以下文章:
《Java中synchronized实现类锁的两种方式及原理解析》
《Java中synchronized实现对象锁的两种方式及原理解析》
《Java多线程wait()和notify()系列方法使用教程》
《Java中Synchronized的可重入性和不可中断性的分析和代码验证》
《Java多线程访问Synchronized同步方法的八种使用场景》