腾讯面试原题:如何在Java中优雅地停止一个正在运行的线程:最佳实践与深入分析

停止一个线程的操作意味着在任务完成之前中止其执行,简而言之,就是放弃当前的操作。虽然可以通过 Thread.stop() 方法强行停止线程,但由于该方法存在安全隐患,且已被标记为废弃,因此不建议使用。Java提供了三种安全方法来终止正在运行的线程:

  1. 利用退出标志,让线程在 run 方法完成后正常退出。
  2. 使用 stop 方法强制中断线程,但因其过时且不安全,故不推荐。
  3. 使用 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 类中提供了两种方法用于判断线程是否已被中断:

  1. this.interrupted(): 测试当前线程是否已经中断。
  2. 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中线程的停止是一个复杂的问题,但通过合理的方法和策略,可以在实现并发操作的同时,确保程序的稳定性和数据一致性。