Java 并发基础知识(四):线程的中断

  1. 正常情况下,一个线程怎样退出?
    答:正常情况下,通过线程的 start() 方法启动一个线程后,线程开始执行 run() 方法,run() 方法运行结束后线程退出

  2. 接上题,那为什么还需要结束一个线程呢?或者说需要特别结束一个线程的场景有哪些?
    答:

    • 很多线程运行的模式是死循环,比如在生产者/消费者模式中,消费者主体就是一个死循环,它不停地从队列中接受任务、执行任务。在停止程序时,我们需要一种优雅的方法以关闭该线程。
    • 在一些图形用户界面程序中,线程是用户启动的,完成一些任务,比如从远程服务器上下载一个文件,在下载过程中,用户可能会希望取消该任务。
    • 在一些场景中,比如从第三方服务器查询一个结果,我们希望在限定的时间内得到结果,如果得不到,我们会希望取消该任务
    • 有时,我们会启动多个线程做同一件事,比如类似抢火车票,我们可能会让多个好友帮忙从多个渠道买火车票,只要有一个渠道买到了,我们会通知取消其他渠道。
  3. 取消/关闭一个线程的机制?
    答:

    • JavaThread 类定义了如下方法:public final void stop(),但被标记为了过时,不应该再使用。
    • Java 中,停止一个线程的主要机制是中断
    • 中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出
  4. Thread 类中定义的关于中断的方法有哪些?它们的区别是?
    答:每个线程都有一个标志位,表示该线程是否被中断了

    • public boolean isInterrupted():返回对应线程的中断标志位是否为 true
    • public void interupt():表示中断对应的线程。
    • public static boolean interrupted():返回当前线程的中断标志位是否为 true但它还有一个重要的副作用,就是清空中断标志位。即,连续两次调用 interrupted(),第一次返回的结果为 true,第二次一般就是 false(除非同时又发生了一次中断)。
  5. 线程对中断的反应是怎样的?
    答:

    • interrupt() 对线程的影响与线程的状态和在进行的 IO 操作有关。我们主要考虑线程的状态,IO 操作的影响和具体 IO 以及操作系统有关。
    • 线程的状态有:

      • RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度。
      • WAITING/TIMED_WAITING:线程在等待某个条件或超时。
      • BLOCKED:线程在等待锁,试图进入同步块。
      • NEW/TERMINATED:线程还未启动或已结束。
    • RUNNABLE

      • 如果线程在运行中,且没有执行 IO 操作,interupt() 只是会设置线程的中断标志位,没有任何其他作用
      • 线程应该在运行过程中合适的位置检查中断标志位。比如,如果主体代码是一个循环,可以在循环开始处进行检查。
    • WAITING/TIMED_WAITING

      • 线程调用 join()/wait()/sleep() 方法会进入 WAITINGTIMED_WAITING 状态,在这些状态时,对线程对象调用 interrupt() 会使得该线程抛出 InterruptedException 异常。抛出异常后,中断标志位会被清空,而不是被设置
      • InterruptedException 是一个受检异常,线程必须进行处理。处理异常的基本思路是:如果知道怎么处理,就进行处理,如果不知道,就应该向上传递,通常情况下不应该捕获异常后然后忽略。
      • 捕获到 InterruptedException,通常表示希望结束该线程。
    • BLOCKED

      • 如果线程在等待锁,对线程对象调用 interrupt() 只是会设置线程的中断标志位,线程依然会处于 BLOCK 状态
      • interrupt() 并不能使一个在等待锁的线程真正“中断”,即 interrupt() 方法只会设置线程的中断标志,而并不会使它从锁等待队列中出来
      • 在使用 synchronized 关键字获取锁的过程中不响应中断请求,这是 synchronized 的局限性。如果这对程序是一个问题,应该使用显示锁 Lock 接口,Lock 支持以响应中断的方式获取锁
    • NEW/TERMINATER

      • 如果线程尚未启动(NEW,或者已经结束(TERMINATER,则调用 interrupt() 对它没有任何效果,中断标志位也不会被设置
  6. 实际开发中,怎样正确地取消/关闭线程?
    答:

    • interrupt() 方法不一定会真正“中断”线程,它只是一种协作机制
    • 如果不明白线程在做什么,不应该贸然调用线程的 interrupt() 方法
    • 对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,外部调用者应该调用这些方法而不是直接调用 interrupt() 方法。