线程是“一段运行中的代码”,或者说是一个运行中的函数。既然是在运行中,就存在一个最基本的问题:运行到一半的线程能否强制杀死? 答案:肯定是不能。

  • 在Java中,有 stop()、destory() 之类的函数,但这些函数都是官方明确不建议使用的。
  • 原因很简单,如果强制杀死线程,则线程中所使用的资源,例如文件描述符、网络连接等不能正常关闭。
  • 因此,一个线程一旦运行起来,就不要去强行打断它,合理的关闭办法是让其运行完(也就是函数执行完毕),干净地释放掉所有资源,然后退出。如果是一个不断循环运行的线程,就需要用到线程间的通信机制,让主线程通知其退出。

线程中止:没有任何语言方面的需求一个被中断的线程应该终止

  • 中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断
  • 在 java 中线程中断是一种协作机制,也就是说调用线程对象的 interrupt 方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己

线程中断的错误方法:jdk 不建议使用

  • stop()
  • destory()

线程中断的正确方法:

  • interrupt()
  • 使用标识位

错误的线程中止

1、stop():中止线程,并且清除线程监控器锁的信息,可能导致线程安全问题,jdk不建议使用

  1. public class ThreadStopTest {
  2. class StopThread extends Thread {
  3. private int i, j;
  4. @Override
  5. public void run() {
  6. synchronized (this) {
  7. // 增加同步锁,确保线程安全
  8. i++;
  9. try {
  10. // 休眠10s,模拟耗时操作
  11. TimeUnit.SECONDS.sleep(10);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. j++;
  16. }
  17. }
  18. public void print(){
  19. System.out.println("i = " + i + "\nj = " + j);
  20. }
  21. }
  22. @Test
  23. public void test() throws InterruptedException {
  24. final StopThread stopThread = new StopThread();
  25. // 启动线程
  26. stopThread.start();
  27. // 休眠1s,确保变量i自增成功
  28. TimeUnit.SECONDS.sleep(1);
  29. // 暂停线程
  30. stopThread.stop(); // 错误中止
  31. // stopThread.interrupt(); // 正确中止
  32. while (true){
  33. // 确保线程已经中止
  34. if (!stopThread.isAlive()) {
  35. break;
  36. }
  37. }
  38. // 输出结果
  39. stopThread.print();
  40. }
  41. }

输出:
1、错误的线程中止:stopThread.stop();
image-20200908214833172.png
2、正确的线程中止:stopThread.interrupt();
image-20200908214731609.png

标识位中断

标识位中断:使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量,然后有秩序地中止任务

  • 标识位中断的缺陷:如果线程在 while 循环中阻塞在某个地方,例如里面调用了 object.wait() 函数,那它可能永远没有机会再执行 while(!stopped) 代码,也就一直无法退出循环

    1. public class FlagStop extends Thread {
    2. // 标识位,volatile(标识该变量从内存中读取)保障可见性、有序性、读写的原子性
    3. private volatile boolean stopped = false;
    4. private final static long SEQ = 1000;
    5. @Override
    6. public void run() {
    7. // 每隔一秒检测一下中断信号量
    8. while (!stopped){
    9. System.out.println("线程正在运行!");
    10. long time = System.currentTimeMillis();
    11. /**
    12. * 使用while循环模拟sleep方法,这里不要使用sleep,否则在阻塞时会抛
    13. * 出InterruptedException异常而退出循环,这样while检测stop条件就不会执行,
    14. * 失去了意义
    15. */
    16. while ((System.currentTimeMillis() - time < SEQ)){}
    17. }
    18. System.out.println("线程收到关闭信号!");
    19. }
    20. public void stopFlag(){
    21. this.stopped = true;
    22. }
    23. public static void main(String[] args) throws InterruptedException {
    24. final FlagStop flagStop = new FlagStop();
    25. flagStop.start();
    26. // 主线程休眠2s后中止子线程
    27. TimeUnit.SECONDS.sleep(2);
    28. flagStop.stopFlag();
    29. // 等待flagStop线程中止
    30. flag.join();
    31. }
    32. }

thread.interrupt() 中断非阻塞状态

轻量级阻塞和重量级阻塞:

  • 能够被中断的阻塞称为轻量级阻塞,对应的线程状态是 WAITING 或者 TIMED_WAITING
  • synchronized 这种不能被中断的阻塞称为重量级阻塞,对应的状态是 BLOCKED

java 中断原理:java 的中断是一种协作机制,每个线程都有一个 boolean 的中断状态(该状态不在 Thread 的属性上),thread.interrupt() 方法只是将该状态设置为 true,然后程序自身需要不断去轮询标识位并做出处理,因此只有能抛出 InterruptedException 异常的方法才能被中断,对正常运行的线程调用 thread.interrupt() 并不会中止线程,只会改变标识位的状态

可中断的阻塞:只有声明了会抛出 InterruptedException 异常的函数才会抛出异常,一般这些方法都是可以被中断的,换言之,即可中断的方法会对 interrupt 方法的调用做出相应(例如 sleep 响应 interrupt 的操作包括清楚中断状态,抛出 InterruptedException),异常都是由可中断的方法自己抛出来的,而非由 interrupt 方法直接引起。以下为会抛出 InterruptedException 异常的函数:

  • Thread 中的方法:
    • public static native void sleep(long millis) throws InterruptedException;
    • public final void join() throws InterruptedException(){}
  • Object 中的方法:
    • public final void wait() throws InterruptedException{}

不可中断的阻塞:

  • 不可中断的操作,包括进入 synchronized 段以及 Lock.lock(),inputSteam.read() 等,调用interrupt() 对于这几个方法无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去

interrupt 方法的作用:唤醒轻量级阻塞

  1. 如果目标线程在调用 Object 类的wait()、wait(long)或wait(long,int)等方法、join()、join(long,int) 或sleep(long,int)方法时被阻塞,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常,然后线程的中断标志位会由true重置为false,因为线程为了处理异常已经重新处于就绪状态
  2. 如果目标线程被 IO 或者 NIO 中 Channel 所阻塞,同样,IO 操作会被中断或者返回特殊异常值,达到终止线程的目的
  3. 对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)
  4. 以上条件都不满足,则会设置此线程的中断状态

中断后异常处理:

  • 直接抛出而非 try 来捕获
  • 在 catch 子句中重新设置中断状态 Thread.currentThread.interrupt(); ,让外界通过判断 Thread.currentThread.isInterrupted() 标识来决定线程是否继续

检测线程是否被中断:Thread类的方法

  • static boolean interrupted:测试当前线程是否已经中断,会清除中断状态,即先返回当前线程的中断状态,然后将 interrupt 标识设为 false
  • boolean isInterrupted():测试线程是否已经中断,只会获取,不会清除中断标识【建议使用此方法】

基本使用:

  1. Runnable runnable = ()->{
  2. try {
  3. TimeUnit.SECONDS.sleep(2);
  4. /**
  5. * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上
  6. * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显
  7. * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时
  8. */
  9. while (!Thread.currentThread().isInterrupted()){
  10. // 如果调用了阻塞方法但没有阻塞时的处理
  11. ....
  12. }
  13. } catch (InterruptedException e) {
  14. // 线程在wait或者sleep期间被中断,进行处理
  15. e.printStackTrace();
  16. } finally {
  17. // 线程结束前做一些清理工作
  18. System.out.println("资源释放等!");
  19. }
  20. };

上述的 while 循环在 try 块里,如果 try 在 while 循环里时,因该在 catch 块里重新设置一下中断标示,因为抛出 InterruptedException 异常后,中断标示位会自动清除

  1. Runnable runnable = ()->{
  2. while(!Thread.currentThread().isInterrupted()){
  3. try{
  4. TimeUnit.SECONDS.sleep(2);
  5. } catch (InterruptedException e) {
  6. // 重新设置中断标识
  7. Thread.currentThread().interrupt();
  8. }
  9. }
  10. };

使用 thread.interrupt() 中断非阻塞线程:

  1. public class InterruptStop extends Thread{
  2. private static final long SEQ = 1000;
  3. @Override
  4. public void run() {
  5. while (!Thread.currentThread().isInterrupted()){
  6. System.out.println("线程正在运行!");
  7. long time = System.currentTimeMillis();
  8. // 使用while循环模拟sleep
  9. while ((System.currentTimeMillis() - time < SEQ)){}
  10. }
  11. System.out.println("线程收到中断请求进行中断!");
  12. }
  13. public static void main(String[] args) throws InterruptedException {
  14. final InterruptStop interruptStop = new InterruptStop();
  15. interruptStop.start();
  16. // 主线程休眠2s
  17. TimeUnit.SECONDS.sleep(2);
  18. // 发出中断请求
  19. interruptStop.interrupt();
  20. // 等待子线程结束
  21. interruptStop.join();
  22. }
  23. }

thread.interrupt() 方法本质:唤醒轻量级阻塞,即将线程的中断标识设置为 true ,在线程收到轻量级阻塞的地方抛出一个异常InterruptedException,并且中断状态也将被清除(设置为false),这样线程就得以退出阻塞的状态

  1. class Example3 extends Thread {
  2. public static void main(String args[]) throws Exception {
  3. Example3 thread = new Example3();
  4. System.out.println("Starting thread...");
  5. thread.start();
  6. Thread.sleep(3000);
  7. System.out.println("Asking thread to stop...");
  8. thread.interrupt();// 等中断信号量设置后再调用
  9. Thread.sleep(3000);
  10. System.out.println("Stopping application...");
  11. }
  12. public void run() {
  13. while (!Thread.currentThread().isInterrupted()) {
  14. System.out.println("Thread running...");
  15. try {
  16. /*
  17. * 如果线程阻塞,将不会去检查中断信号量stop变量,所以thread.interrupt()
  18. * 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并
  19. * 进行异常块进行 相应的处理
  20. */
  21. Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常
  22. } catch (InterruptedException e) {
  23. System.out.println("Thread interrupted...");
  24. /*
  25. * 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法
  26. * 过程中受阻,则其中断状态将被清除
  27. */
  28. System.out.println(this.isInterrupted());// false
  29. //中不中断由自己决定,如果需要真真中断线程,则需要重新设置中断位,如果
  30. //不需要,则不用调用
  31. Thread.currentThread().interrupt();
  32. }
  33. }
  34. System.out.println("Thread exiting under request...");
  35. }
  36. }