不提倡stop()方法
Thread.STOP()之类的api会造成一些不可预知的bug,所以很早便Deprecated了,真要纠结为什么请看这边文章为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?
当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,并抛出特殊的ThreadDeath()异常。这里的“立即”因为太“立即”了。比如,假如一个线程正在执行:
synchronized void {
x = 3;
y = 4;
}
由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记 线程的stop方法,以后我们再也不要说“停止线程”了。
interupt()中断线程
一个线程从运行到真正的结束,应该有三个阶段:
- 正常运行.
- 处理结束前的工作,也就是准备结束.
- 结束退出.
Thread类定义了如下关于中断的方法:
如果线程在运行中,interrupt()只是会设置线程的中断标志位,没有任何其它作用。那么如何让线程退出运行?对于一般逻辑,只要线程状态已经中断,我们就可以让它退出,因此可以使用isInterrupted()来判断线程的中断标志位,如下代码所示:
public class InterruptRunnableDemo extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// ... 单次循环代码
}
System.out.println("done ");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new InterruptRunnableDemo();
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
这样InterruptRunnableDemo发现isInterrupted()为true,就会退出运行。
但是,在线程正在执行wait、sleep、join这些方法时,对线程对象调用interrupt()会使得该线程抛出InterruptedException,需要注意的是,抛出异常后,中断标志位会被清空(线程的中断标志位会由true重置为false,变成非中断状态,因为线程为了处理异常已经重新处于就绪状态。),而不是被设置为中断状态。如果catch语句没有处理异常,则下一次循环中isInterrupted()为false,线程会继续执行,可能你N次抛出异常,也无法让线程停止。比如说,执行如下代码:
Thread t = new Thread (){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//exception被捕获,但是为输出为false 因为标志位会被清空
System.out.println(isInterrupted());
}
}
};
t.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
t.interrupt();//置为true
这种情况下,大概有两种处理方式:
- 使用“二次惰性检测”(double check),把线程正确退出的方法称为“双重安全退出”,即不以isInterrupted ()为循环条件。而以一个标记作为循环条件。(这是JAVA推荐的方法)
- 捕获异常,进行合适的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,或者直接return,使得其他代码有办法知道它发生了中断
第一种方式的示例代码如下:
public class ThreadA extends Thread {
private boolean isInterrupted=false;
int count=0;
public void interrupt(){
isInterrupted = true;
super.interrupt();
}
public void run(){
System.out.println(getName()+"将要运行...");
while(!isInterrupted){
System.out.println(getName()+"运行中"+count++);
try{
Thread.sleep(400);
}catch(InterruptedException e){
System.out.println(getName()+"从阻塞中退出...");
System.out.println("this.isInterrupted()="+this.isInterrupted());
}
}
System.out.println(getName()+"已经终止!");
}
}
第二种方式的示例代码如下:
public class InterruptWaitingDemo extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 模拟任务代码
Thread.sleep(2000);
} catch (InterruptedException e) {
// ... 清理操作
System.out.println(isInterrupted());//false
// 重设中断标志位为true
Thread.currentThread().interrupt();
}
}
System.out.println(isInterrupted());//true
}
public static void main(String[] args) {
InterruptWaitingDemo thread = new InterruptWaitingDemo();
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
thread.interrupt();
}
}
或者直接return:
@Override
public void run(){
for(int i = 0; i < 100; i++){
System.out.println(this.getname()+":"+i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
System.out.println("异常结束!");
return; //在异常中直接返回,从而打断线程
}
}
}
NEW/TERMINATE
如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。比如说,以下代码的输出都是false。
public class InterruptNotAliveDemo {
private static class A extends Thread {
@Override
public void run() {
}
}
public static void test() throws InterruptedException {
A a = new A();
a.interrupt();
System.out.println(a.isInterrupted());
a.start();
Thread.sleep(100);
a.interrupt();
System.out.println(a.isInterrupted());
}
public static void main(String[] args) throws InterruptedException {
test();
}
}
如何正确地取消/关闭线程
1. 以上,我们可以看出,interrupt方法不一定会真正”中断”线程,它只是一种协作机制,如果 不明白线程在做什么,不应该贸然的调用线程的interrupt方法,以为这样就能取消线程。
2. 对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,类似于InterruptReadDemo中演示的cancel方法,外部调用者应该调用这些方法而不是直接调用interrupt。
3. Java并发库的一些代码就提供了单独的取消/关闭方法,比如说,Future接口提供了如下方法以取消任务:boolean cancel(boolean mayInterruptIfRunning);
4. 再比如,ExecutorService提供了如下两个关闭方法:
void shutdown();
List<Runnable> shutdownNow();