腾讯面试原题:如何在Java中优雅地停止一个正在运行的线程:最佳实践与深入分析
停止一个线程的操作意味着在任务完成之前中止其执行,简而言之,就是放弃当前的操作。虽然可以通过 Thread.stop()
方法强行停止线程,但由于该方法存在安全隐患,且已被标记为废弃,因此不建议使用。Java提供了三种安全方法来终止正在运行的线程:
- 利用退出标志,让线程在
run
方法完成后正常退出。 - 使用
stop
方法强制中断线程,但因其过时且不安全,故不推荐。 - 使用
interrupt
方法来中断线程。
1. 停止不了的线程
interrupt()
方法的效果并不是立即停止线程的执行。调用该方法仅是在当前线程中设置了一个中断标志,而不是强制停止线程。
public class MyThread extends Thread {
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
System.out.println("i=" + (i + 1));
}
}
}
public class Run {
public static void main(String args[]) {
Thread thread = new MyThread();
thread.start();
try {
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
...
i=499994
i=499995
i=499996
i=499997
i=499998
i=499999
i=500000
2. 判断线程是否处于停止状态
Thread
类中提供了两种方法用于判断线程是否已被中断:
this.interrupted()
: 测试当前线程是否已经中断。this.isInterrupted()
: 测试指定线程是否已经中断。
这两个方法的区别是什么?
this.interrupted()
是用于测试当前线程的中断状态,调用后会清除中断标志。
public class MyThread extends Thread {
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
i++;
}
}
}
public class Run {
public static void main(String args[]) {
Thread thread = new MyThread();
thread.start();
try {
Thread.sleep(2000);
thread.interrupt();
System.out.println("stop 1??" + thread.interrupted());
System.out.println("stop 2??" + thread.interrupted());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
stop 1??false
stop 2??false
在上面的例子中,尽管对 thread
对象调用了 interrupt()
,但由于 interrupted()
方法是针对当前正在执行的 main
线程,因此返回的都是 false
。
要让 main
线程产生中断效果,可以使用以下代码:
public class Run2 {
public static void main(String args[]) {
Thread.currentThread().interrupt();
System.out.println("stop 1??" + Thread.interrupted());
System.out.println("stop 2??" + Thread.interrupted());
System.out.println("End");
}
}
运行效果:
stop 1??true
stop 2??false
End
这里 interrupted()
方法正确地判定了当前线程的中断状态。第二个布尔值为 false
是因为 interrupted()
方法在第一次调用后会清除中断状态。
接下来,看看 isInterrupted()
方法的结果:
public class Run3 {
public static void main(String args[]) {
Thread thread = new MyThread();
thread.start();
thread.interrupt();
System.out.println("stop 1??" + thread.isInterrupted());
System.out.println("stop 2??" + thread.isInterrupted());
}
}
运行结果:
stop 1??true
stop 2??true
isInterrupted()
不会清除状态,所以连续调用返回的都是 true
。
3. 能停止的线程—异常法
通过之前学习的知识,可以在 run
方法中使用 for
循环判断线程的中断状态,如果线程已被中断,则停止执行后续代码:
public class MyThread extends Thread {
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("线程已经终止, for循环不再执行");
break;
}
System.out.println("i=" + (i + 1));
}
}
}
public class Run {
public static void main(String args[]) {
Thread thread = new MyThread();
thread.start();
try {
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
...
i=202053
i=202054
i=202055
i=202056
线程已经终止, for循环不再执行
尽管上面的示例成功停止了线程,但如果 for
循环后面还有其他语句,这些语句仍然会被执行。以下是一个例子:
public class MyThread extends Thread {
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("线程已经终止, for循环不再执行");
break;
}
System.out.println("i=" + (i + 1));
}
System.out.println("这是for循环外面的语句,也会被执行");
}
}
运行结果:
...
i=180136
i=180137
i=180138
i=180139
线程已经终止, for循环不再执行
这是for循环外面的语句,也会被执行
为了解决这个问题,可以通过抛出 InterruptedException
来确保执行流完全停止:
public class MyThread extends Thread {
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("线程已经终止, for循环不再执行");
throw new InterruptedException();
}
System.out.println("i=" + (i + 1));
}
System.out.println("这是for循环外面的语句,也会被执行");
} catch (InterruptedException e) {
System.out.println("进入MyThread.java类中的catch了。。。");
e.printStackTrace();
}
}
}
4. 在沉睡中停止
如果线程处于 sleep()
状态时被停止,会触发捕获的异常:
public class MyThread extends Thread {
public void run() {
super.run();
try {
System.out.println("线程开始。。。");
Thread.sleep(200000);
System.out.println("线程结束。");
} catch (InterruptedException e) {
System.out.println("在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:" + this.isInterrupted());
e.printStackTrace();
}
}
}
运行结果:
线程开始。。。
在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at thread.MyThread.run(MyThread.java:12)
可以看到,线程在 sleep
状态被中断后,触发了异常,并且清除了中断状态,返回 false
。
5. 能停止的线程—暴力停止
使用 stop()
方法强制停止线程是一种暴力方式,但极不安全:
public class MyThread extends Thread {
private int i = 0;
public void run() {
super.run();
try {
while (true) {
System.out.println("i=" + i);
i++;
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String args[]) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
Thread.sleep(2000);
thread.stop();
}
}
运行结果:
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
Process finished with exit code 0
6. stop()方法与java.lang.ThreadDeath异常
调用 stop()
方法将会抛出 java.lang.ThreadDeath
异常,但通常不需要显式捕获该异常。
public class MyThread extends Thread {
private int i = 0;
public void run() {
super.run();
try {
this.stop();
} catch (ThreadDeath e) {
System.out.println("进入异常catch");
e.printStackTrace();
}
}
}
public class Run {
public static void main(String args[]) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
}
}
由于 stop()
方法存在的不安全性,可能会导致一些清理工作无法完成,甚至可能造成数据的不一致,强烈建议在开发中避免使用该方法。
7. 释放锁的不良后果
使用 stop()
方法释放锁可能造成数据不一致的结果。如果数据受到破坏,程序执行流程可能会出现错误,因此需要格外小心:
public class SynchronizedObject {
private String name = "a";
private String password = "aa";
public synchronized void printString(String name, String password) {
try {
this.name = name;
Thread.sleep(100000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
public class MyThread extends Thread {
private SynchronizedObject synchronizedObject;
public MyThread(SynchronizedObject synchronizedObject) {
this.synchronizedObject = synchronizedObject;
}
public void run() {
synchronizedObject.printString("b", "bb");
}
}
public class Run {
public static void main(String args[]) throws InterruptedException {
SynchronizedObject synchronizedObject = new SynchronizedObject();
Thread thread = new MyThread(synchronizedObject);
thread.start();
Thread.sleep(500);
thread.stop();
System.out.println(synchronizedObject.getName() + " " + synchronizedObject.getPassword());
}
}
输出结果:
b aa
由于使用 stop()
方法,程序中的数据较可能产生不一致,因此不建议使用。
8. 使用return停止线程
结合 interrupt()
和 return
方法可以有效地停止线程:
public class MyThread extends Thread {
public void run() {
while (true) {
if (this.isInterrupted()) {
System.out.println("线程被停止了!");
return;
}
System.out.println("Time: " + System.currentTimeMillis());
}
}
}
public class Run {
public static void main(String args[]) throws InterruptedException {
Thread thread = new MyThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
输出结果:
...
Time: 1467072288503
Time: 1467072288503
Time: 1467072288503
线程被停止了!
尽管使用 return
可以停止线程,推荐使用异常抛出的方法,以便在 catch
块中可以向上抛出异常,确保线程停止事件能够传播。
尽管在Java中线程的停止是一个复杂的问题,但通过合理的方法和策略,可以在实现并发操作的同时,确保程序的稳定性和数据一致性。