栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

这样终止线程,竟然会导致服务宕机?

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

这样终止线程,竟然会导致服务宕机?

在开始之前,我们先来看以下代码会有什么问题?

public class ThreadStopExample {
    public static void main(String[] args) throws InterruptedException {
 Thread t1 = new Thread(() -> {
     try {
  System.out.println("子线程开始执行");
  // 模拟业务处理
  Thread.sleep(1000);
     } catch (Exception e) { }
     // 伪代码:重要的业务方法
     System.out.println("子线程的重要业务方法");
 });
 t1.start();
 // 让子线程先运行一点业务
 Thread.sleep(100);
 // 终止子线程
 t1.stop();
 // 等待一段时间,确保子线程“执行完”
 Thread.sleep(3000);
 System.out.println("主线程执行完成");
    }
}

或许你已经发现了,上面这段代码使用了 Thread.stop() 来终止线程,在 Java 程序中是不允许这样终止线程的。什么?你问为什么不能这样?

首先来说 IDE 都会鄙视你了,它会阻止你使用 Thread.stop() !

什么?你不信。那么来看这张图:

好吧,那为什么不能这样用呢?总得给我一个敷衍的理由吧?

问题一:破坏了程序的完整性

其实是这样的,以文章刚开头的那段代码来说,它的执行结果是:

子线程开始执行

主线程执行完成

我们发现了一个惊天的大问题,最重要的那段伪代码竟然没执行,如下图所示:

可以看出使用 stop() 终止线程之后,线程剩余的部分代码会放弃执行,这样会造成严重的且不易被发现的惊天大 Bug,假如没有执行的那段代码是释放系统资源的代码,或者是此程序的主要逻辑处理代码。这就破坏了程序基本逻辑的完整性,导致意想不到的问题发生,而且它还很隐秘,不易被发现和修复。

有人说,这还不简单,我加个 finally 不就完了吗?

这???杠精哪都有,今年特别多。

行,既然这个说服不了你,咱接着往下看。

问题二:破坏了原子逻辑

我们知道在 Java 中 synchronized 属于独占式可重入悲观锁,如果我们使用它修饰代码,妥妥的多线程没问题,但如果碰到 stop() 方法就不一定了,直接来看代码吧。

public class ThreadStopExample {
    public static void main(String[] args) throws InterruptedException {
 MyThread myThread = new MyThread();
 Thread t2 = new Thread(myThread);
 // 开启线程
 t2.start();
 for (int i = 0; i < 10; i++) {
     Thread t = new Thread(myThread);
     t.start();
 }
 // 结束线程
 t2.stop();
    }

    
    static class MyThread implements Runnable {
 // 计数器
 int num = 0;

 @Override
 public void run() {
     // 同步代码块,保证原子操作
     synchronized (MyThread.class) {
  // 自增
  num++;
  try {
      // 线程休眠 0.1 秒
      Thread.sleep(100);
  } catch (InterruptedException e) {
      e.printStackTrace();
  }
  // 自减
  num--;
  System.out.println(Thread.currentThread().getName() + " | num=" + num);
     }
 }
    }
}

以上程序的执行结果为:

Thread-5 | num=1

Thread-4 | num=1

Thread-2 | num=1

Thread-1 | num=1

Thread-8 | num=1

Thread-6 | num=1

Thread-9 | num=1

Thread-3 | num=1

Thread-7 | num=1

Thread-10 | num=1

从结果可以看出,以上代码经过 synchronized 修饰的 ++ 和 – 操作,到最后打印的结果 num 竟然不是 0,而是 1。

这是因为 stop() 方法会释放此线程中的所有锁,导致程序执行紊乱,破坏了程序的原子操作逻辑

以上的这些问题,导致了 JDK 废弃了 stop() 的方法,它的废弃源码如下:


@Deprecated
public final void stop() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
 checkAccess();
 if (this != Thread.currentThread()) {
     security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
 }
    }
    // A zero status value corresponds to "NEW", it can't change to
    // not-NEW because we hold the lock.
    if (threadStatus != 0) {
 resume(); // Wake up thread if it was suspended; no-op otherwise
    }

    // The VM can handle all thread states
    stop0(new ThreadDeath());
}

可以看出 stop() 方法被 @Deprecated 注释修饰了,而被此注解修饰的代码表示为过时方法,不建议被使用。从 stop() 的备注信息可以看出,官方也不建议使用 stop() ,说它是一个非安全的方法。

正确终止线程

那如何终止线程呢?这里提供 2 个正确的方法:

  1. 设置退出标识退出线程;
  2. 使用 interrupt() 方法终止线程。
1.自定义退出标识

我们可以自定义一个布尔变量来标识是否需要退出线程,实现代码如下:

// 自定义退出标识退出线程
static class FlagThread extends Thread {
    public volatile boolean exit = false;

    public void run() {
 while (!exit) {
     // 执行正常的业务逻辑
 }
    }
}

可以看出我们使用了关键字 volatile 对线程进行了修饰,这样就可以保证多线程的执行安全了,在我们需要让线程退出时,只需要把变量 exit 赋值为 true 就可以了。

2.interrupt 终止线程

当我们使用 interrupt() 方法时,以上两个示例的执行结果就正常了,执行代码如下:

public class ThreadStopExample {
    public static void main(String[] args) throws InterruptedException {
 // 问题一:破坏了程序的完整性
 Thread t1 = new Thread(() -> {
     try {
  System.out.println("子线程开始执行");
  // 模拟业务处理
  Thread.sleep(1000);
     } catch (Exception e) { }
     // 伪代码:重要业务方法
     System.out.println("子线程的重要业务方法");
 });
 t1.start();
 // 让子线程先运行一点业务
 Thread.sleep(100);
 // 终止子线程
 t1.interrupt();
 // 等待一段时间,确保子线程“执行完”
 Thread.sleep(3000);
 System.out.println("主线程执行完成");

 // 问题二:破坏了原子逻辑
 MyThread myThread = new MyThread();
 Thread t2 = new Thread(myThread);
 // 开启线程
 t2.start();
 for (int i = 0; i < 10; i++) {
     Thread t = new Thread(myThread);
     t.start();
 }
 // 结束线程
 t2.interrupt();
    }

    
    static class MyThread implements Runnable {
 // 计数器
 int num = 0;

 @Override
 public void run() {
     // 同步代码块,保证原子操作
     synchronized (MyThread.class) {
  // 自增
  num++;
  try {
      // 线程休眠 0.1 秒
      Thread.sleep(100);
  } catch (InterruptedException e) {
      System.out.println(e.getMessage());
  }
  // 自减
  num--;
  System.out.println(Thread.currentThread().getName() + " | num=" + num);
     }
 }
    }
}

以上程序的执行结果为:

子线程开始执行

子线程的重要业务方法

主线程执行完成

sleep interrupted

Thread-1 | num=0

Thread-9 | num=0

Thread-10 | num=0

Thread-7 | num=0

Thread-6 | num=0

Thread-5 | num=0

Thread-4 | num=0

Thread-2 | num=0

Thread-3 | num=0

Thread-11 | num=0

Thread-8 | num=0

可以看出以上的执行都符合我们的预期,这才是正确的终止线程的方式。

总结

本文我们讲了线程的三种终止方式,自定义退出标识的方式、使用 stop() 的方式或 interrupt() 的方式。其中 stop() 的方式会导致程序的完整性和原子性被破坏的问题,并且此方法被 JDK 标识为过期方法,不建议使用,而 interrupt() 方法无疑是最适合我们的终止线程的方式。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号