多线程核心3:线程的正确停止方式

正确方式

  • 如何正确的停止线程?

    原理:使用interrupt来通知,而不是强制

    原因是通常情况下会认为发出停止请求的线程并不清楚被停止的线程的状态和细节,所以要把停止的决定权交给线程本身,而不是其它线程

  • 通常线程会在什么情况下停止?

    1. run方法中所有的代码运行完毕
    2. run方法中有异常出现,但未被捕获处理
  • 使用正确方法停止线程会有以下几种情况:

    1. run方法内没有sleepwait方法,停止线程

      main线程和Thread-0线程要相互配合才能起到作用

      /**
       * 在子线程Thread-0运行一秒钟后,main线程发出中断请求
       * Thread-0线程内的run方法进行判断:!Thread.currentThread().isInterrupted()
       * 如果有中断请求到来则停止线程执行
       */
      public class RightWayStopThreadWithoutSleep implements Runnable {
              
              
          @Override
          public void run() {
              
              
              int num = 0;
              while (num <= Integer.MAX_VALUE / 2 && !Thread.currentThread().isInterrupted()){
              
              
                  if (num % 10000 == 0){
              
              
                      System.out.println(num);
                  }
                  num ++;
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              
              
              Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
              thread.start();
      
              Thread.sleep(1000);
              thread.interrupt();
          }
      }
      
    2. 在子线程休眠的过程中,主线程发出让子线程中断的请求

      会抛出InterruptedException异常,用try/catch捕获处理,!Thread.currentThread().isInterrupted()判断可以去掉,情况和3类似

      /**
       * 主线程在0.5秒后发出线程中断请求
       * 子线程这时已经处在为时1秒的休眠期内
       */
      public class RightWayStopThreadWithSleep{
              
              
          public static void main(String[] args) throws InterruptedException {
              
              
              Runnable runnable = () -> {
              
              
                  int num = 0;
                  try {
              
              
                      while (num <= 300 && !Thread.currentThread().isInterrupted()){
              
              
                          if (num % 100 == 0) {
              
              
                              System.out.println(num + "----" + Thread.currentThread().getName());
                          }
                          num++;
                      }
      
                      Thread.sleep(1000);
                  }catch (InterruptedException e){
              
              
                      e.printStackTrace();
                  }
              };
              Thread thread = new Thread(runnable);
              thread.start();
              Thread.sleep(500);
              thread.interrupt();
          }
      }
      
    3. 线程在每次迭代后都阻塞

      /**
       * 每次循环都会进入一段休眠时间,这时如果接到主线程的中断请求,也会抛异常
       * !Thread.currentThread().isInterrupted()的判断便没有意义,线程停止不是由于检测到中断信号
       * 而是因为线程已经处在休眠状态
       */
      public class RightWayStopThreadWithSleepEveryLoop {
              
              
          public static void main(String[] args) throws InterruptedException {
              
              
              Runnable runnable = () -> {
              
              
                  int num = 0;
                  try {
              
              
                      while (num <= 30000){
              
              
                          if (num % 100 == 0) {
              
              
                              System.out.println(num);
                          }
                          num++;
                          Thread.sleep(10);
                      }
      
      
                  }catch (InterruptedException e){
              
              
                      e.printStackTrace();
                  }
              };
              Thread thread = new Thread(runnable);
              thread.start();
              Thread.sleep(5000);
              thread.interrupt();
          }
      }
      
    4. 例外情况,在while里面进行try/catch会使得中断请求失效,这是因为异常在被捕获之后,Interrupted标志位会被清除,所以在下一次循环的判断中便检测不到线程停止的标志,线程继续执行

      /**
       * 在while循环中进行try/catch,可以捕获抛出异常
       * 但之后方法继续执行,线程没有被停止
       */
      public class CantInterrupt {
              
              
          public static void main(String[] args) throws InterruptedException {
              
              
              Runnable runnable = () -> {
              
              
                  int num = 0;
                  while (num <= 10000 && !Thread.currentThread().isInterrupted()){
              
              
                      if (num % 100 == 0){
              
              
                          System.out.println(num);
                      }
                      num++;
                      try {
              
              
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
              
              
                          e.printStackTrace();
                      }
                  }
              };
              Thread thread = new Thread(runnable);
              thread.start();
              Thread.sleep(1000);
              thread.interrupt();
          }
      }
      
  • 实际开发中的两种最佳实践

    1. 优先选择:传递中断,在出现异常的方法中不处理,抛给高层次的run方法进行捕获处理

      /**
       * 在throwInMethod中出现异常后,不应该在throwInMethod中进行try/catch处理,这是低层次的处理
       * 应该抛出给高层次的run方法来处理(保存日志等),避免异常被忽略
       */
      public class RightWayStopThreadInProd1 implements Runnable {
              
              
          @Override
          public void run() {
              
              
              while (true){
              
              
                  System.out.println("go");
                  try {
              
              
                      throwInMethod();
                  } catch (InterruptedException e) {
              
              
                      System.out.println("保存日志");
                      e.printStackTrace();
                  }
              }
          }
      
          private void throwInMethod() throws InterruptedException {
              
              
              Thread.sleep(2000);
          }
      
          public static void main(String[] args) throws InterruptedException {
              
              
              Thread thread = new Thread(new RightWayStopThreadInProd1());
              thread.start();
              Thread.sleep(1000);
              thread.interrupt();
          }
      }
      
    2. 不想传递或无法传递:恢复中断,在出现异常的方法中捕获处理,但要重新设置中断

      /**
       * 在不想或无法传递中断时,应该选择恢复中断,避免中断被忽略
       */
      public class RightWayStopThreadInProd2 implements Runnable {
              
              
          @Override
          public void run() {
              
              
              while (true){
              
              
                  if (Thread.currentThread().isInterrupted()){
              
              
                      System.out.println("Interrupted,程序运行结束");
                      break;
                  }
                  reInterrupt();
              }
          }
      
          private void reInterrupt() {
              
              
              try {
              
              
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
              
              
                  //重新设置中断
                  Thread.currentThread().interrupt();
                  e.printStackTrace();
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              
              
              Thread thread = new Thread(new RightWayStopThreadInProd2());
              thread.start();
              Thread.sleep(1000);
              thread.interrupt();
          }
      
      }
      

错误方式

  • 错误的停止线程的方法

    1. 被弃用的stopsuspendresume方法

    2. volatile设置boolean标记位

      使用生产者-消费者问题来说明:阻塞队列容量为10,生产者生产快,消费者消费慢,这时便要请求停止生产者线程,使用标记位会失效

      public class WrongWayVolatileCantStop {
              
              
          public static void main(String[] args) throws InterruptedException {
              
              
              ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
      
              Producer producer = new Producer(storage);
              Thread producerThread = new Thread(producer);
              producerThread.start();
              Thread.sleep(1000);
      
              Consumer consumer = new Consumer(storage);
              while (consumer.needMoreNums()){
              
              
                  System.out.println(consumer.storage.take() + "被消费了");
                  Thread.sleep(100);
              }
              System.out.println("消费者不需要更多了");
      
              producer.canceled = true;
          }
      }
      
      /**
       * 在while循环里,没有检测线程停止请求的语句,只有在入口的判断条件有!canceled
       * 因此线程便一直阻塞在storage.put(num)那里
       */
      class Producer implements Runnable{
              
              
          public volatile boolean canceled = false;
      
          BlockingQueue storage;
      
          public Producer(BlockingQueue storage) {
              
              
              this.storage = storage;
          }
      
          @Override
          public void run() {
              
              
              int num = 0;
              try {
              
              
                  while (num <= 100000 && !canceled) {
              
              
                      if (num % 100 == 0) {
              
              
                          //阻塞在这里
                          storage.put(num);
                          System.out.println(num);
                      }
                      num++;
                  }
              } catch (InterruptedException e){
              
              
                  e.printStackTrace();
              } finally {
              
              
                  System.out.println("生产者运行结束");
              }
          }
      }
      
      class Consumer{
              
              
          BlockingQueue storage;
      
          public Consumer(BlockingQueue storage) {
              
              
              this.storage = storage;
          }
      
          public boolean needMoreNums(){
              
              
              if (Math.random() > 0.95){
              
              
                  return false;
              }
              return true;
          }
      }
      

      而使用interrupt来改造即可达到正确停止线程的效果

      /**
       * 描述:     用中断来修复刚才的无尽等待问题
       */
      public class WrongWayVolatileFixed {
              
              
      
          public static void main(String[] args) throws InterruptedException {
              
              
              WrongWayVolatileFixed body = new WrongWayVolatileFixed();
              ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
      
              Producer producer = body.new Producer(storage);
              Thread producerThread = new Thread(producer);
              producerThread.start();
              Thread.sleep(1000);
      
              Consumer consumer = body.new Consumer(storage);
              while (consumer.needMoreNums()) {
              
              
                  System.out.println(consumer.storage.take() + "被消费了");
                  Thread.sleep(100);
              }
              System.out.println("消费者不需要更多数据了。");
      
      
              producerThread.interrupt();
          }
      
      
          class Producer implements Runnable {
              
              
      
              BlockingQueue storage;
      
              public Producer(BlockingQueue storage) {
              
              
                  this.storage = storage;
              }
      
      
              @Override
              public void run() {
              
              
                  int num = 0;
                  try {
              
              
                      //将volatile的boolean标志位改为监听中断请求状态
                      while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
              
              
                          if (num % 100 == 0) {
              
              
                              storage.put(num);
                              System.out.println(num + "是100的倍数,被放到仓库中了。");
                          }
                          num++;
                      }
                  } catch (InterruptedException e) {
              
              
                      e.printStackTrace();
                  } finally {
              
              
                      System.out.println("生产者结束运行");
                  }
              }
          }
      
          class Consumer {
              
              
      
              BlockingQueue storage;
      
              public Consumer(BlockingQueue storage) {
              
              
                  this.storage = storage;
              }
      
              public boolean needMoreNums() {
              
              
                  if (Math.random() > 0.95) {
              
              
                      return false;
                  }
                  return true;
              }
          }
      }
      
  • 停止线程相关函数

    判断是否已经被中断?

    1. interrupted(),这是清除interrupted status 的唯一办法,源码如下:

      /**
       * 测试当前线程是否已被中断。通过此方法可以清除线程的中断状态。
       * 换句话说,如果此方法被连续调用两次,则第二次调用将返回false(除非当前线程在第一次调用清除其中断的状态之后再次被中断,并且在第二次调用之前检查它)。
       *
       * 由于该线程未处于活动状态而忽略了线程中断此方法将反映该线程返回false。
       */
      
      
      /**
           * Tests whether the current thread has been interrupted.  The
           * <i>interrupted status</i> of the thread is cleared by this method.  In
           * other words, if this method were to be called twice in succession, the
           * second call would return false (unless the current thread were
           * interrupted again, after the first call had cleared its interrupted
           * status and before the second call had examined it).
           *
           * <p>A thread interruption ignored because a thread was not alive
           * at the time of the interrupt will be reflected by this method
           * returning false.
           *
           * @return  <code>true</code> if the current thread has been interrupted;
           *          <code>false</code> otherwise.
           * @see #isInterrupted()
           * @revised 6.0
           */
          public static boolean interrupted() {
              
              
              return currentThread().isInterrupted(true);
          }
      
    2. isInterrupted(),不会清除interrupted status

      /**
           * Tests whether this thread has been interrupted.  The <i>interrupted
           * status</i> of the thread is unaffected by this method.
           *
           * <p>A thread interruption ignored because a thread was not alive
           * at the time of the interrupt will be reflected by this method
           * returning false.
           *
           * @return  <code>true</code> if this thread has been interrupted;
           *          <code>false</code> otherwise.
           * @see     #interrupted()
           * @revised 6.0
           */
          public boolean isInterrupted() {
              
              
              return isInterrupted(false);
          }
      
  • Thread.interrupted()的目标对象是线程

  1. threadOne.isInterrupted():Thread-0线程被请求中断,返回true

  2. threadOne.interrupted()Thread.interrupted():由于interrupted()方法为静态方法,调用它的对象为RightWayInterrupted类,因为main线程未被请求中断,所以这两个方法都是输出false

  3. threadOne.isInterrupted():因为调用该方法不会清除interrupted status,因此仍然输出true

/**
 * 描述:     注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象
 */
public class RightWayInterrupted {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    

        Thread threadOne = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (; ; ) {
    
    
                }
            }
        });

        // 启动线程
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        //获取中断标志并重置
        System.out.println("isInterrupted: " + threadOne.interrupted());
        //获取中断标志并重直
        System.out.println("isInterrupted: " + Thread.interrupted());
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

----------
输出结果:
isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true

猜你喜欢

转载自blog.csdn.net/weixin_44863537/article/details/112119389