专栏首页Java技术栈为什么不推荐使用 stop、suspend 方法中断线程?

为什么不推荐使用 stop、suspend 方法中断线程?

我们知道像stop、suspend这几种中断或者阻塞线程的方法在较高java版本中已经被标记上了@Deprecated过期标签,那么为什么她们曾经登上了java的历史舞台而又渐渐的推出了舞台呢?

到底是人性的扭曲还是道德的沦丧呢,亦或是她们不思进取被取而代之呢,如果是被取而代之,那么取而代之的又是何方人也,本文我们将一探究竟。

一、stop的落幕

首先stop方法的作用是什么呢,用java源码中的一句注释来了解一下:Forces the thread to stop executing.,即强制线程停止执行,'Forces’似乎已经透漏出了stop方法的蛮狠无理。

那么我们再看看java开发者是怎们解释stop被淘汰了的:

This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the uncheckedThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait.

我们从中可以看出以下几点:

  1. stop这种方法本质上是不安全的
  2. 使用Thread.stop停止线程会导致它解锁所有已锁定的监视器,即直接释放当前线程已经获取到的所有锁,使得当前线程直接进入阻塞状态

我们举例来看一下上边提到的两点:

public static void main(String[] args) throws InterruptedException {
        Object o1=new Object();
        Object o2=new Object();
        Thread t1=new Thread(()->{
              synchronized (o1)
              {
                  synchronized (o2)
                  {
                      try {
                          System.out.println("t1获取到锁");
                          Thread.sleep(5000);
                          System.out.println("t1结束");
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
        });
        t1.start();
        Thread.sleep(1000);
        Thread t2=new Thread(()->{
            synchronized (o1)
            {
                synchronized (o2)
                {
                    try {
                        System.out.println("t2获取到锁");
                        Thread.sleep(5000);
                        System.out.println("t2结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t2.start();
        t1.stop();
    }

运行结果:

可以看到,当线程t1在获取到o1和o2两个锁开始执行,在还没有执行结束的时候,主线程调用了t1的stop方法中断了t1的执行,释放了t1线程获取到的所有锁,中断后t2获取到了o1和o2锁,开始执行直到结束,而t1却夭折在了sleep的时候,sleep后的代码没有执行。

因此使用stop我们在不知道线程到底运行到了什么地方,暴力的中断了线程,如果sleep后的代码是资源释放、重要业务逻辑等比较重要的代码的话,亦或是其他线程依赖t1线程的运行结果,那直接中断将可能造成很严重的后果。

那么不建议使用stop中断线程我们应该怎么去优雅的结束一个线程呢,我们可以存java开发者的注释中窥探到一种解决方案:

Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait.

可以看到java开发者推荐我们使用以下两种方法来优雅的停止线程。另外,多线程系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。

1.定义一个变量,由目标线程去不断的检查变量的状态,当变量达到某个状态时停止线程。

代码举例如下:

volatile static boolean flag=false;
public static void main(String[] args) throws InterruptedException {
        Object o1=new Object();
        Thread t1=new Thread(()->{
              synchronized (o1)
              {
                  try {
                      System.out.println("t1获取到锁");
                      while (!flag)
                          Thread.sleep(5000);//执行业务逻辑
                      System.out.println("t1结束");
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
        });
        t1.start();
        Thread.sleep(1000);
        Thread t2=new Thread(()->{
            synchronized (o1)
            {
                try {
                    System.out.println("t2获取到锁");
                    Thread.sleep(5000);//执行业务逻辑
                    System.out.println("t2结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();
        flag=true;
    }

运行结果:

2.使用interrupt方法中断线程。

代码举例如下:

public static void main(String[] args) throws InterruptedException {
        Object o1=new Object();
        Thread t1=new Thread(()->{
              synchronized (o1)
              {
                  System.out.println("t1获取到锁");
                  while (!Thread.currentThread().isInterrupted()) {
                      for (int i = 0; i < 100; i++) {
                          if(i==50)
                              System.out.println();
                          System.out.print(i+" ");
                      }
                      System.out.println();
                  }
                  System.out.println("t1结束");
              }
        });
        t1.start();
        Thread t2=new Thread(()->{
            synchronized (o1)
            {
                try {
                    System.out.println("t2获取到锁");
                    Thread.sleep(5000);//执行业务逻辑
                    System.out.println("t2结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();
        t1.interrupt();
    }

运行结果:

我们用while (!Thread.currentThread().isInterrupted())来不断判断当前线程是否被中断,中断的话则让线程自然消亡并释放锁。可以看到调用interrupt方法后并不会像stop那样暴力的中断线程,会等到当前运行的逻辑结束后再检查是否中断,非常的优雅。

注:运行举例代码可能不会打印出数字,这是因为t1线程运行到while(!Thread.currentThread().isInterrupted())时,主线程已经调了interrupt方法,因此多次运行可能会打印出数字。

二、suspend的落幕

suspend方法的作用是挂起某个线程直到调用resume方法来恢复该线程,但是调用了suspend方法后并不会释放被挂起线程获取到的锁,正因如此就给suspend和resume这哥俩贴上了容易引发死锁的标签,当然这也正是导致suspend和resume退出历史舞台的罪魁祸首。同样我们看看java开发者为suspend的淘汰给出的理由:

This method has been deprecated, as it is inherently deadlock-prone. If the target thread holds a lock on the monitor protecting a critical system resource when it is suspended, no thread can access this resource until the target thread is resumed. If the thread that would resume the target thread attempts to lock this monitor prior to calling resume, deadlock results. Such deadlocks typically manifest themselves as “frozen” processes.

从中我们可以得出以下结论:

  1. suspend具有天然的死锁倾向
  2. 当某个线程被suspend后,该线程持有的锁不会被释放,其他线程也就不能访问这些资源
  3. suspend某个线程后,如果在resume的过程中出现异常导致resume方法执行失败,则lock无法释放,导致死锁

接下来模拟一下由suspend引起的死锁场景,Talk is cheap,show my code:

public static void main(String[] args) throws InterruptedException {
        Object o1=new Object();
        Object o2=new Object();
        Thread t1=new Thread(()->{
              synchronized (o1)
              {
                  System.out.println("t1获取到o1锁开始执行");
                  try {
                      Thread.sleep(5000);//模拟执行业务逻辑
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("t1执行结束");
              }
        });
        t1.start();
        Thread t2=new Thread(()->{
            synchronized (o2)
            {
                System.out.println("t2获取到o2开始执行");
                try {
                    Thread.sleep(2000);//执行耗时业务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1)
                {
                    System.out.println("t2获取到o1锁开始继续执行");
                }
                System.out.println("t2执行结束");
            }
        });
        t2.start();

        Thread.sleep(1000);
        t1.suspend();
        //假设抛出了一个未知异常
        int i=1/0;
        t1.resume();
    }

运行结果:

可以看到,整个程序卡的死死的,在调用resume恢复t1线程之前抛出了一个未知异常,导致t1一直挂起进而无法释放o1锁,而t2需要获取到o1锁后才能继续执行,但苦苦等待,奈何o1被t1拿捏的死死的,从此整个程序就陷入了无尽的等待中----死锁。最后,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Java 多线程系列面试题和答案,非常齐全。

参考:

https://docs.oracle.com/javase/9/docs/api/java/lang/doc-files/threadPrimitiveDeprecation.html https://mp.weixin.qq.com/s/G_R5saE9HGULKwlYDhxOfQ

原文链接:https://blog.csdn.net/qq_40400960/article/details/112651249

本文分享自微信公众号 - Java技术栈(javastack),作者:点击关注 ????

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 为什么不推荐使用 stop、suspend 方法中断线程?

    来源 | https://blog.csdn.net/qq_40400960/article/details/112651249

    程序猿DD
  • 带你搞懂Java多线程(二)

    Runnable和Callable是对任务的抽象,只有Thread是对线程的抽象。

    longzeqiu
  • Java程序员面试题大全系列之Java基础类库(一)

    1、java 中有几种类型的流?JDK 为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

    动力节点Java培训
  • 安全地终止线程

    线程执行完后,将会终止。那么线程除了正常终止外,还有没有别的方式可以终止线程呢?

    黑洞代码
  • JAVA 高并发设计

    同步和异步通常用来形容一次方法调用,同步方法,调用者必须等到方法调用返回后,才能继续后续的行为,异步方法调用会立即返回,调用者就可以继续后续的操作。

    李鹏
  • 高并发Java(2):多线程基础

    使用线程的原因是,进程的切换是非常重量级的操作,非常消耗资源。如果使用多进程,那么并发数相对来说不会很高。而线程是更细小的调度单元,更加轻量级,所以线程会较为广...

    用户5640963
  • Java多线程相关面试题

    反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象 处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出...

    宇宙之一粟
  • 为什么不推荐使用存储过程?

    之所以有这个题目,我既不是故意吸引眼球,也不想在本文对存储过程进行教科书般论述。最近项目中遇到的存储过程问题,让我想起了去年在武汉出差时一位同事的发问:

    芋道源码
  • 大数据基础:Java多线程入门

    在大数据开发学习当中,Java基础是非常重要的一部分,打好了Java基础,才能在后续的大数据框架技术学习阶段,也能有所主力。而Java当中的一个重要知识点,就是...

    成都加米谷大数据
  • Java面试系列16-jdbc、hibernate、流、线程实现、多态、继承事程序执行顺序等

    1 JDBC,Hibernate 分页怎样实现? 方法分别为: 1) Hibernate 的分页: Query query = session.createQ...

    Java帮帮
  • Java多线程——多线程方法详解

    从结果可知:Mythread的构造方法是被main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是线程自动调用的

    说故事的五公子
  • Java多线程实战:多线程方法详解

    从结果可知:Mythread的构造方法是被main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是线程自动调用的

    田维常
  • 如何停止一个线程

    线程当中一般都会写循环,如果不写循环,一句话能搞定的事,就没必要再开线程来处理。 stop方法已经过时, run方法结束。 开启多线程时,运行代码通常是循环结构...

    潇洒
  • jvm源码解析(三)线程状态

    有些书上Waitting和Timed_Watting是归类在Blocked下的所以说是五种状态,有些书是单独拿出来的,所以是七种状态。大多数情况下承认五种状态。

    用户6203048
  • java多线程系列_线程的生命周期(4)

    与人有生老病死一样,线程也同样要经历开始(等待)、运行、挂起和停止四种不同的状态。这四种状态都可以通过Thread类中的方法进行控制。下面给出了Thread类中...

    Hongten
  • 为什么有的程序员不推荐使用Lombok!

    我有个学弟,在一家小型互联网公司做Java后端开发,最近他们公司新来了一个技术总监,这位技术总监对技术细节很看重,一来公司之后就推出了很多"政策",比如定义了很...

    Java3y
  • 线程的基本操作

    用户1216491
  • 如何停止一个正在运行的线程?

    停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但最好不要用它。

    Java技术栈
  • 面试官:如何停止一个正在运行的线程?我一脸蒙蔽...

    停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但最好不要用它。虽然它确实可以停...

    后端码匠

扫码关注云+社区

领取腾讯云代金券