03-停止线程

nobility 发布于 2021-04-24 1797 次阅读


停止线程

停止线程的原则

使用interrupt()来通知线程进行中断,而不是强制停止,在Java中若想停止线程最多就是告诉该线程该中断了,被告知的线程拥有是否停止线程的决定权;之所以这样设计是因为,要中断的线程通常需要完成一些保存工作再进行停止,若强行停止可能会出现混乱,比如数据丢失等情况

停止线程的相关方法

方法名 描述
boolean isInterrupted() 判断线程中断状态
static bolean interrupted() 判断当前线程中断状态,并消除当前线程的中断标记位
void interrupt() 中断线程

停止线程的正确方式

能够让线程正确停止方式只有以下两种情况

  • run()方法所有代码执行完毕
  • 线程中抛出异常,并未进行异常的捕获

开发多线程时停止线程设计

中断检查

run()方法中添加收到中断信号后进行return返回即可

public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread(() -> {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {  //为了让程序运行时间足够长
      if (Thread.currentThread().isInterrupted()) {  //每次循环中检查是否收到中断信号
        System.out.println("收到中断信号,进行保存工作");
        return;  //若收到中断信号该线程的就会执行结束
      }
      if (i % 1000000 == 0) {
        System.out.println(i);
      }
    }
  });

  thread.start();
  Thread.sleep(1000);
  thread.interrupt();  //1秒后传递中断信号
}
中断阻塞
阻塞方法列表

只有下面列表中的方法可以(由于重载方法比较多,将方法的参数省略):将当前线程进入阻塞状态后可响应到中断信号

  • Object.wait()
  • Thread.sleep()
  • Thread.join()
  • java.util.concurrent.BlockingQueue.take()/put()
  • java.util.concurrent.locks.Lock.lockInterruptibly()
  • java.util.concurrent.CountDownLatch.await()
  • java.util.concurrent.CyclicBarrier.await()
  • java.util.concurrent.Exchanger.exchange()
  • java.nio.channels.InterruptibleChannel类的相关方法
  • java.nio.channels.Selector类的相关方法
单次阻塞

run()方法出现阻塞情况的代码,添加对InterruptedException中断异常的捕获,当收到中断信号后就会进入该catch块,在catch块中return返回即可

public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread(() -> {
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {  //若收到中断信号后会就进入该catch块
      e.printStackTrace();
      System.out.println("收到中断信号,进行保存工作");
      return;  //若没有return该线程并不会结束,除非后面没有代码了
    }

    for (int i = 0; i < Integer.MAX_VALUE; i++) {
      if (i % 1000000 == 0) {
        System.out.println(i);
      }
    }
  });

  thread.start();
  Thread.sleep(1000);
  thread.interrupt();  //1秒后传递中断信号
}
多次阻塞

在迭代中的catch块相当于后面还有代码,之所以会出现这种情况是因为,Java中的Thread.sleep()方法一旦响应中断就会将中断标记位清除,所以即时再次进行中断检查也无济于事

public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread(() -> {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
      if (Thread.currentThread().isInterrupted()) return;
      //若在catch块中没有return,即时在此处进行中断检查也无法停止线程
      System.out.println(i);
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        System.out.println("收到中断信号,进行保存工作");
        e.printStackTrace();
        //return;  //若没有return该线程并不会结束
      }
    }
  });

  thread.start();
  Thread.sleep(1000);
  thread.interrupt();  //1秒后传递中断信号
}

开发方法时停止线程的设计

不应该屏蔽中断,而是优先采用传递中断或恢复中断,否则会出现用户在调用时,线程无法向预计那样停止下来,比如下面代码这样

public class MyLib {
  public void myMethod() {
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("myMethod执行完毕");
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
      MyLib myLib = new MyLib();
      while (true) {
        if (Thread.currentThread().isInterrupted()) return;
        //用户中断检查,并return,但无法停止
        myLib.myMethod();
      }
    });

    thread.start();
    Thread.sleep(1000);
    thread.interrupt();  //1秒后传递中断信号
  }
}
传递中断

应该优先选择传递中断:将InterruptedException中断异常的向外抛出,而并非捕获,那么用户在run()方法中调用我们的方法时就会强制要求对该异常强制处理

public class MyLib {
  public void myMethod() throws InterruptedException {
    Thread.sleep(500);
    System.out.println("myMethod执行完毕");
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
      MyLib myLib = new MyLib();
      while (true) {
        try {
          myLib.myMethod();
        } catch (InterruptedException e) {
          e.printStackTrace();
          return;  //用户中断检查,并return
        }
      }
    });

    thread.start();
    Thread.sleep(1000);
    thread.interrupt();  //1秒后传递中断信号
  }
}
恢复中断

其次选择恢复中断:在catch块中再次调用interrupt()方法来恢复Thread.sleep()方法清除的中断标记

public class MyLib {
  public void myMethod() {
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      e.printStackTrace();
      Thread.currentThread().interrupt(); //恢复中断
    }
    System.out.println("myMethod执行完毕");
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
      MyLib myLib = new MyLib();
      while (true) {
        if (Thread.currentThread().isInterrupted()) return;  //用户中断检查,并return
        myLib.myMethod();
      }
    });

    thread.start();
    Thread.sleep(1000);
    thread.interrupt();  //1秒后传递中断信号
  }
}

停止线程的错误方式

使用弃用的线程停止方法

  • 使用suspend()resume()方法并不像stop()方法那样会破坏对象,但是会让一个线程带着锁进行挂起,很容易造成死锁,比如:唤醒该线程的线程需要这把锁就造成了死锁
  • 使用被弃用的stop(),会导致线程运行一般突然停止,无法完成一个基本单位的操作,会造成脏数据
public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
      System.out.println("事务开始");
      System.out.println("账户B:-100");
      try {
        Thread.sleep(500);  //模拟转账延迟
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("账户A:+100");
      System.out.println("事务结束");
    }
  });

  thread.start();
  Thread.sleep(1000);
  thread.stop();  //1秒后强制停止线程
}

volatile标记位方式

使用标记位方式在线程没有被阻塞的情况下是可行的,但是一旦线程进入阻塞状态后,只有线程被吵醒后才会停止线程,比如阻塞队列进入阻塞状态后,该线程就无法停止

public class MyRunnable implements Runnable {
  private volatile boolean flag = false;

  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {  //为了让程序运行时间足够长
      if (flag) return;   //每次循环中检查标记位
      System.out.println(i);  //每次循环休眠20秒
      try {
        Thread.sleep(20000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    MyRunnable myRunnable = new MyRunnable();
    new Thread(myRunnable).start();
    Thread.sleep(1000);
    myRunnable.flag = true;  //1秒后希望线程停止
  }
}
此作者没有提供个人介绍
最后更新于 2021-04-24