理解java线程的中断(interrupt)
一个线程在未正常结束之前, 被强制终止是很危险的事情. 因为它可能带来完全预料不到的严重后果比如会带着自己所持有的锁而永远的休眠,迟迟不归还锁等。 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了
那么不能直接把一个线程搞挂掉, 但有时候又有必要让一个线程死掉, 或者让它结束某种等待的状态 该怎么办呢?一个比较优雅而安全的做法是:使用等待/通知机制或者给那个线程一个中断信号, 让它自己决定该怎么办。
中断线程的使用场景:
- 在某个子线程中为了等待一些特定条件的到来, 你调用了Thread.sleep(10000), 预期线程睡10秒之后自己醒来, 但是如果这个特定条件提前到来的话, 来通知一个处于Sleep的线程。
- 又比如说.主线程通过调用子线程的join方法阻塞自己以等待子线程结束, 但是子线程运行过程中发现自己没办法在短时间内结束, 于是它需要想办法告诉主线程别等我了. 这些情况下, 就需要中断.
中断是通过调用Thread.interrupt()方法来做的. 这个方法通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了.
对于非阻塞中的线程, 只是改变了中断状态, 即Thread.isInterrupted()将返回true;
对于可取消的阻塞状态中的线程, 比如在主线程main中调用了子线程的这些函数: 子线程Thread.sleep(), 子线程Object.wait(), 子线程Thread.join(), 然后调用子线程的Thread.interrupted()会对中断状态进行复,子线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为true.
对非阻塞中的线程中断的Demo:
public class Thread3 extends Thread{
public void run(){
while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println("Someone interrupted me.");
}
else{
System.out.println("Thread is Going...");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread3 t = new Thread3();
t.start();
Thread.sleep(3000);
t.interrupt();
}
}
分析如上程序的结果:
在main线程里调用了子线程t的run方法,但由于子线程t的run方法里面 没有调用函数Thread.sleep()、Object.wait()、Thread.join()这三个函数 并且 主线程main里面 也没有调用子线程的这三个函数,所以子线程是非阻塞的,所以当main主线程调用子线程t.interrupt()方法时,子线程t里面,并没有抛出InterruptedException异常。
首先我们看看interrupt究竟在干什么。
当我们调用t.interrput()的时候,线程t的中断状态(interrupted status) 会被置位。我们可以通过Thread.currentThread().isInterrupted() 来检查这个布尔型的中断状态。
打印结果是:循环打印,只是截取了一块结果
很显然,在上面代码中,while循环有一个决定因素就是需要不停的检查自己的中断状态。当外部线程调用该线程的interrupt 时,使得中断状态置位即变为true。这是该线程将终止循环,不在执行循环中的do more work了。
这说明: interrupt中断的是线程的某一部分业务逻辑,前提是线程需要检查自己的中断状态(isInterrupted())。
但是当子线程t被阻塞的时候,比如在子线程t的run方法里面,子线程自己执行了Object.wait, Thread.join和Thread.sleep三种方法之一阻塞时或在main主线程中调用了子线程t的这3个方法之一让子线程处于阻塞时。调用它的interrput()方法。可想而知,没有占用CPU运行的线程是不可能给自己的中断状态置位的。这就会产生一个InterruptedException异常。
class Example3 extends Thread {
volatile boolean stop = false;
public static void main(String args[]) throws Exception {
Example3 thread = new Example3();
System.out.println("Starting thread...");
thread.start();
System.out.println("Asking thread to stop...");
/*
* 如果线程阻塞,将不会检查此变量,调用interrupt之后,线程就可以尽早的终结被阻
* 塞状 态,能够检查这一变量。
* */
//为了防止主线程执行太快,子线程还没开始执行呢,主线程已经将stop设置为true了 子线程的while就进不去了
Thread.sleep(1000);
thread.stop = true;
/*
* 这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退
* 出阻 塞的状态
* */
thread.interrupt();
Thread.sleep(3000);
System.out.println("Stopping application...");
}
public void run() {
while (!stop) {
System.out.println("Thread running...");
try {
//子线程 调用了sleep方法 让自己处于阻塞状态 阻塞1分钟
Thread.sleep(60000);
} catch (InterruptedException e) {
// 接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态
System.out.println("Thread interrupted...");
}
}
System.out.println("Thread exiting under request...");
}
}
打印结果:
主线程main调用子线程run方法,初始stop=false,子线程进入while循环,子线程阻塞1分钟,此时Main主线程设置了stop=true,并调用了子线程的interrupt()方法,子线程收到中断信号后,自己正处于阻塞状态,所以直接会抛出InterruptedException异常,while判断也进不去了,所以子线程直接执行 System.out.println("Stopping application...");后退出。
所以当代码调用中须要抛出一个InterruptedException, 你可以选择把中断状态复位, 也可以选择向外抛出InterruptedException, 由外层的调用者来决定.