0%

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

1. 正常情况下,一个线程怎样退出

  • 正常情况下,通过线程的 start() 方法启动一个线程后,线程开始执行 run() 方法
  • run() 方法运行结束后线程退出

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

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

3. 取消/关闭一个线程的机制

  • Java 的 Thread 类定义了如下方法:public final void stop(),但被标记为了过时,不应该再使用
  • 在 Java 中,停止一个线程的主要机制是中断
  • 中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出

4. Thread 类中定义的关于中断的方法有哪些,它们的区别是

  • 每个线程都有一个标志位,表示该线程是否被中断了
  • 关于中断的方法有如下几个
    • public boolean isInterrupted():返回对应线程的中断标志位
    • 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() 方法
-------------------- 本文结束感谢您的阅读 --------------------