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

胡班
• 阅读 1041

我们知道像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();
    }

运行结果:

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

可以看到,当线程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开发者推荐我们使用以下两种方法来优雅的停止线程:

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;
    }

运行结果:

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

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();
    }

运行结果:

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

我们用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();
    }

运行结果:

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

可以看到,整个程序卡的死死的,在调用resume恢复t1线程之前抛出了一个未知异常,导致t1一直挂起进而无法释放o1锁,而t2需要获取到o1锁后才能继续执行,但苦苦等待,奈何o1被t1拿捏的死死的,从此整个程序就陷入了无尽的等待中----死锁。

参考:

https://docs.oracle.com/javas...

https://mp.weixin.qq.com/s/G_...

原文链接:https://blog.csdn.net/qq_4040...

版权声明:本文为CSDN博主「浪舟子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2021最新版)

2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!

3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式发布,全新颠覆性版本!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
4年前
java debug体系为什么不能debug到jdk里所有的代码
作为java码农肯定碰到过当我们debug到一些class的时候,发现当进入到某个方法里是看不到声明的入参名,取而代之的是arg0,arg1等,继续深入更是看不到局部变量,这主要是java类编译的时候没有加g参数导致的,而为什么我们自己在eclipse中写的代码却是可以正常跟踪呢,原因很简单,因为eclipse自行编译的时候是带g参数编译的。  
Wesley13 Wesley13
4年前
java 中断线程的几种方式 interrupt()
中断  中断(Interrupt)一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作。线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序。虽然初次看来它可能显得简单,但是,你必须进行一些预警以实现期望的结果。你最好还是牢记以下的几点告诫。  首先,忘掉Thread.stop方法。虽然它确实停止了一个正
御弟哥哥 御弟哥哥
5年前
如何正确停止Java线程,终止Java线程的三种方法
如何正确停止Java线程,终止Java线程的三种方法在Java中有以下3种方法可以终止正在运行的线程:1.使用退出标志,使线程正常退出,也就是当run()方法完成后线程终止。2.使用stop()方法强行终止线程,但不推荐,该方法已被弃用,原因见后文。3.使用interrupt方法中断线程。以下内容翻译自J
Wesley13 Wesley13
4年前
Java并发编程:Lock
一.synchronized的缺陷synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?  在上面一篇文章中,我们了解到如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁
Wesley13 Wesley13
4年前
Java并发系列7
如果要让线程阻塞,我们在讲线程基础的时候已经分析过了。如果要让线程暂停呢,不是blocked,而是waiting,这时候有什么办法?Thread类有一个弃用的方法suspend()是线程暂停的意思,他被弃用的原因是线程挂起的时候并不会释放持有的锁资源,而且suspend()挂起的线程状态依然是runnable,这也是不合理的。那么有没有一种简单的
Wesley13 Wesley13
4年前
Java线程停止方法之Interrupt方法
  最近在学习Java多线程相关的知识点,其中关于线程停止的方法网上也有不少大牛给出了详细的解答,而我这边就其中Interrupt方法的注意点给自己提个醒。  首先还是大概的罗列下停止线程的方法:  1、使用stop()方法等,不过已经不再被推荐使用,和suspend、resume一样。  2、使用退出标志终止线程,引入一个共享变量,volati
Wesley13 Wesley13
4年前
2.Java 并行程序基础
1.初始线程:线程的基本操作1.新建线程2.终止线程stop造成数据不一致3.线程中断publicvoidThread.interrupt()//中断线程publicbooleanThread.isTnterrup
Wesley13 Wesley13
4年前
JAVA程序设计练习题集答案
一、判断题1.String字符串在创建后可以被修改。(0)2.引用一个类的属性或调用其方法,必须以这个类的对象为前缀。(0final类名)3.当调用一个正在进行线程的stop()方法时,该线程便会进入休眠状态。(0)4.如果一个类声明实现一个接口,但没有实现接口中的所有方法,那么这个类必须是abst
Stella981 Stella981
4年前
Clone
Java中对象的创建clone顾名思义就是复制,在Java语言中,clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢?1使用new操作符创建一个对象2使用clone方法复制一个对象那么这两种方
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
从原理聊JVM(四):JVM中的方法调用原理 | 京东云技术团队
多态是Java语言极为重要的一个特性,可以说是Java语言动态性的根本,那么线程执行一个方法时到底在内存中经历了什么,JVM又是如何确定方法执行版本的呢?
胡班
胡班
Lv1
爱情就像剥洋葱总有一层会让你流泪。
文章
4
粉丝
0
获赞
0