thread.interrupt和线程中断

前言

一直以来都没有系统学习过多线程,以为thread.interrupt()方法就是将sleep、wait等的线程唤醒,然后被唤醒的线程在sleep等方法被调用处会抛出InterrutedException。最近在阅读一些源代码的时候,发现很多地方习惯在捕获到InteruptedException的地方,再次调用Thread.currentThread().interrupt(),很是疑惑。于是,带着好奇,系统的学习了一下thread.interrupt。
try {
    latch.await();
} catch (final InterruptedException ex) {
    Thread.currentThread().interrupt();
}

原理说明

比如主线程worker维护着一个子线程task的句柄,如何在worker里让task线程中断?
我们知道task.stop()这种臭名昭著的方法由于一些安全隐患已经被废弃了。
中断线程,我们通常的思路是维护一个线程之间共享的变量isStop,比如task线程轮询while(!isStop){},worker线程通过isStop = true;来变向的达到中断线程的效果。这是一种很常用也很正确的做法。
我们继续延伸思考,如果task线程在执行的过程中有调用一些阻塞方法,比如Thead.sleep(3000L);那么,即使isStop被设置成了false,task线程也无法立即执行完毕,被中断。

while(!isStop) {
    List tasks = taskDao.selectTodoTask();
    if (tasks.size() == 0) {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {

        }
    } else {
        process(tasks);
    }
}

thread.interrupt()方法就是用来替代thread.stop()方法解决上述问题的。thread.interrupt()的执行将会发生如下行为:
1.将线程内部的中断变量设置为true。(ps:线程对象内部维护着一个类似isStop的中断变量。)
2.如果线程执行到了阻塞方法,那么该方法通过抛出InterruptedException的形式取消阻塞,并将中断变量重新恢复成false。
即通过task.interupt()方法就能够将上例中因为调用sleep()方法而被阻塞的线程立即唤醒,从而让线程能够立马继续判断isStop。当然,因为线程内部已经维护了一个中断变量,上述例子,我们就不用自己维护isStop这个标识变量了。

while(!Thread.currentThread().isInterrupted()) {
    List tasks = taskDao.selectTodoTask();
    if (tasks.size() == 0) {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            /**
             * 阻塞的线程虽然被唤醒,但是中断变量又被重新恢复成false了
             * 所以,我们要在此处再调用一次interrupt方法,保留中断的本意
             */
            Thread.currentThread().interrupt();
        }
    } else {
        process(tasks);
    }
}

延伸扩展

1.我们知道,一些方法调用会导致线程阻塞,sleep、wait、join等,这些方法的声明都统一的对外抛出InterruptedException,意味着,这些方法的调用导致的阻塞是会被interrupt方法给唤醒的。
然而,像io导致的阻塞(读写文件,访问数据库,接口调用等),或者像等待锁导致的阻塞,能不能被interrupt方法唤醒呢?
非nio、synchronized和ReentrantLock.lock(),如果线程处于这些阻塞状态,只能等待io处理完成或者等待到锁之后,程序才能向下执行,从而通过判断中断变量来结束。
nio和ReentrantLock.tryLock(long timeout, TimeUnit unit) throws InterruptedException,如果线程处于这些阻塞状态,是可以被唤醒的。你也可以调用reentrantLock.lockInterruptibly() throws InterruptedException方法,它就相当于一个超时设为无限的tryLock方法。其实从方法声明上我们也可以判断出来,因为对外抛出了InterruptedException。

2.线程池的关闭,有两种方式,一种是shutdownNow(),一种是shutdown()。shutdownNow方法会通过调用运行时线程的interrupt方法来通知线程需要中断;shutdow方法不会,这也很好理解,因为线程池还要靠这些线程来继续执行完等待队列里的任务。
3.Tomcat的关闭,也和线程池的shutdownNow方法类似,会通过调用interrupt方法给正在处理请求的线程发送中断信号。即tomcat关闭的时候,正在处理请求的线程此时的Thread.currentThread().isInterrupted()应该返回true。

你可能感兴趣的:(thread.interrupt和线程中断)