如何正确停止Java线程,终止Java线程的三种方法

御弟哥哥 等级 676 0 0
标签: 线程Java

如何正确停止Java线程,终止Java线程的三种方法

在 Java 中有以下 3 种方法可以终止正在运行的线程:

  1. 使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程终止。
  2. 使用 stop() 方法强行终止线程,但不推荐,该方法已被弃用,原因见后文。
  3. 使用 interrupt 方法中断线程。

以下内容翻译自 JDK1.5官方文档 ,内容有微调。未发现JDK1.8对应文档与JDK1.5的内容有明显不同。

停止一个线程的推荐做法

stop的大多数用法应由简单地修改某些变量以指示目标线程应停止运行的代码代替。 目标线程应定期检查此变量,如果该变量指示要停止运行,则应有序地从其运行方法返回。 (这是Java始终推荐的方法)。为了确保对stop-request进行及时的通信,变量必须是volatile(或必须同步访问变量)

例如,假设您的程序包含以下startstoprun方法:

 private Thread blinker;

    public void start() {
        blinker = new Thread(this);
        blinker.start();
    }

    public void stop() {
        blinker.stop();  // UNSAFE!
    }

    public void run() {
        Thread thisThread = Thread.currentThread();
        while (true) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    } 

可以通过将程序的stop和run方法替换为下面的代码来避免使用Thread.stop

 private volatile Thread blinker;

    public void stop() {
        blinker = null;
    }

    public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    } 


=== 后面的内容可以不看,看的话务必从头到尾按顺序阅读 ===



为什么 Thread.stop 被废弃?

因为它本质上是不安全的。 _停止线程会使它解锁它已锁定的所有监视器_。 (当ThreadDeath异常在堆栈中向上传播时,监视器将被解锁。)如果先前由这些监视器保护的任何对象处于不一致状态,则其他线程现在可能会以不一致状态查看这些对象。 这样的对象被称为_已损坏的对象_。 当线程对损坏的对象进行操作时,可能会导致任意行为。 此行为可能是微妙的,难以检测,或者可能是明显的。 与其他未检查的异常不同,ThreadDeath会无声地杀死线程。 因此,用户没有警告其程序可能已损坏。 在实际损坏发生后的任何时间,腐败会体现出来(注:corruption,意为腐败,类似代码的bad smell,指程序中出现的问题或bug)。

不能只是捕捉ThreadDeath异常并修复损坏的对象吗?

从理论上讲,也许可以,但这会_使编写正确的多线程代码的任务大大复杂化_。该任务几乎是无法克服的,原因有两个:

  1. 线程_几乎可以在任何地方_引发ThreadDeath异常 。考虑到这一点,必须仔细研究所有同步的方法和块。
  2. ThreadDeath从第一个(在catchor finally子句中)清除时,线程可以引发第二个异常。必须重复进行清理,直到成功。确保该代码很复杂。

如何停止等待较长时间的线程(例如,等待输入)?

这就是Thread.interrupt方法的用途。 可以使用上面展示的相同的“基于状态”的信令机制,但是状态更改(在前面的示例中,_blinker = null_)之后可以调用Thread.interrupt来中断等待:

 public void stop() {
        Thread moribund = waiter;
        waiter = null;
        moribund.interrupt();
    } 

为了使该技术起作用,至关重要的是,任何捕获中断异常并且不准备处理中断异常的方法都必须立即重新声明该异常。 我们说重新声明而不是重新抛出,因为并非总是可能重新抛出异常。 如果未声明捕获InterruptedException的方法引发此(checked)Exception,则它应使用下面的提示“重新中断自身”:

Thread.currentThread().interrupt(); 

这样可以确保线程尽快引发InterruptedException

如果线程不响应Thread.interrupt怎么办?

在某些情况下,您可以使用特定于应用程序的技巧。 例如,如果某个线程正在一个已知的套接字上等待,则可以关闭该套接字以使该线程立即返回。 不幸的是,一般来说,真的没有什么技术能奏效。 应该注意的是,在所有等待线程不响应Thread.interrupt的情况下,它也不响应Thread.stop 这样的情况包括故意的拒绝服务攻击,以及Thread.stopThread.interrupt无法正常工作的I / O操作。(_注释_:这段的个人理解,如果程序不响应Thread.interrupt,那就没办法了,自己去想别的方法解决吧。所以还是尽量使用推荐的方法来控制线程停止与运行)

为什么不赞成使用Thread.suspendThread.resume

Thread.suspend本质上容易死锁。 如果目标线程在挂起时,在监视器上持有一个保护关键系统资源的锁,则在恢复目标线程之前,没有线程可以访问该资源。 如果将恢复目标线程的线程在调用resume之前尝试锁定此监视器,则会导致死锁。 这种僵局通常表现为“冻结”进程。

应该使用什么代替Thread.suspendThread.resume

Thread.stop一样,谨慎的方法是让“目标线程”轮询一个指示线程所需状态(活动或挂起)的变量。 当所需的状态被挂起时,线程使用Object.wait等待。 恢复线程后,将使用Object.notify通知目标线程。

例如,假设您的程序包含以下“鼠标按下”事件处理程序,该事件处理程序的功能是可以切换blinker线程的状态:

 private boolean threadSuspended;

    Public void mousePressed(MouseEvent e) {
        e.consume();

        if (threadSuspended)
            blinker.resume();
        else
            blinker.suspend();  // DEADLOCK-PRONE!容易出现死锁

        threadSuspended = !threadSuspended;
    } 

您可以通过将上面的事件处理程序替换为下面的代码来避免使用Thread.suspendThread.resume

 public synchronized void mousePressed(MouseEvent e) {
        e.consume();

        threadSuspended = !threadSuspended;

        if (!threadSuspended)
            notify();
    } 

并添加下面的代码到 "run loop":

 synchronized(this) {
        while (threadSuspended)
            wait();
    } 

wait方法抛出InterruptedException,因此它必须在try ... catch子句中。 可以将其与sleep放在同一个的子句中。 该检查应在sleep之后(而不是在sleep之前),以便在线程“resumed”时立即重新绘制窗口。 生成的run方法如下:

 public void run() {
        while (true) {
            try {
                Thread.currentThread().sleep(interval);

                synchronized(this) {
                    while (threadSuspended)
                        wait();
                }
            } catch (InterruptedException e){
            }
            repaint();
        }
    } 

请注意,“鼠标按下”方法中的notify和run方法中的wait在同步块(synchronized)内部。这种做法是编程语言要求的,并确保wait和notify正确地按顺序执行。 实际上,这消除了可能导致“已暂停”线程错过通知并无限期保持暂停的竞争条件。

随着平台的成熟,尽管Java同步(synchronized)的性能开销在降低,但它永远不会“免费”(注:免费指几乎可以忽略的性能开销)。 一个简单的技巧可以用来删除我们添加到“运行循环”的每个迭代中的同步。 所添加的同步块被稍微复杂一点的代码所代替,仅当线程实际上已被挂起时才进入同步块:

 if (threadSuspended) {
        synchronized(this) {
            while (threadSuspended)
                wait();
        }
    } 

在没有显式同步的情况下,必须将threadSuspended设置为volatile,以确保及时传达suspend-request。

最终生成的run方法为:

 private boolean volatile threadSuspended;

    public void run() {
        while (true) {
            try {
                Thread.currentThread().sleep(interval);

                if (threadSuspended) {
                    synchronized(this) {
                        while (threadSuspended)
                            wait();
                    }
                }
            } catch (InterruptedException e){
            }
            repaint();
        }
    } 

可以结合两种技术来产生可以安全地“停止”或“暂停”的线程吗?

可以的, 这相当简单。 一个微妙之处是目标线程可能在另一个线程试图将其停止时已被挂起。 如果stop方法仅将状态变量(blinker)设置为null,则目标线程将保持挂起状态(在监视器上等待),而不是应有的退出。 如果重新启动了applet,则多个线程可能最终会同时在监视器上等待,从而导致行为不稳定。

要纠正这种情况,stop方法必须确保目标线程在挂起后立即恢复。 目标线程恢复后,必须立即识别出它已停止,并正常退出。 这是生成的run和stop方法的外观:

 public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);

                synchronized(this) {
                    while (threadSuspended && blinker==thisThread)
                        wait();
                }
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

    public synchronized void stop() {
        blinker = null;
        notify();
    } 

如上所述,如果stop方法调用Thread.interrupt,它也不必调用notify,但仍必须同步。 这样可以确保目标线程不会由于竞争条件而错过中断。

关于 Thread.destroy 方法

Thread.destroy从未真正被实现。 如果实现了它,使用Thread.suspend容易发生死锁。 (实际上,它与Thread.suspend大致等效,没有后续的Thread.resume的可能性。)我们目前不实现它,但也不会弃用它(将来将阻止其实现)。 尽管肯定会发生死锁,但有人认为,在某些情况下,某个程序愿意冒着deadlock的风险也不想直接退出。

Java8中的destroy方法源码:

 /**
     * Throws {@link NoSuchMethodError}.
     * 此方法最初设计为在不进行任何清理的情况下销毁此线程。
     * 它所持有的任何监视器都将保持锁定状态。
     * 但是,该方法从未实现。如果要实现,则将以{@link #suspend}的方式发生死锁。
     * 如果目标线程在被销毁时持有一个保护关键系统资源的锁,则没有线程可以再次访问该资源。 
     * 如果另一个线程曾尝试锁定此资源,则会导致死锁。这种死锁通常表现为“冻结”进程。
     */
    @Deprecated
    public void destroy() {
        throw new NoSuchMethodError();
    } 
收藏
评论区

相关推荐

JDK的下载与Java运行环境
**JDK简介** **什么是JDK** JDK是Java Development Kit的缩写,意思是Java开发工具包。JDK就好比作人的心脏,人没有了心脏,生命也就失去存在的意义。Java也一样,JDK就是它的心脏,是它的核心。JDK中不仅有Java运行环境(Java Runtime Environment),还有Java工具与Java的核心类库
Java 基础 SDK区别简介
1. Java SE(Java Platform,Standard Edition),应该先说这个,因为这个是标准版本。 Java EE (Java Platform,Enterprise Edition),java 的企业版本 Java ME(Java Platform,Micro Edition),java的微型版本。 1). JavaSE 可以
Java 高级应用编程 第一章 工具类
**一、Java API** **Java API简介** 1、API (Application Programming Interface) 应用程序接口 2、Java中的API,就是JDK提供的各种功能的Java类 3、JDK帮助文档   JAVA\_API\_CN.chm   官网地址http://www.oracle.com/tech
Java8 新特性
Java 8新特性 ========= 一、Java 8新特性简介 ------------- ### 1\. 简介 Java 8 ( 又称为jdk1.8 ) 是 Java 语言开发的一个主要版本。Java 8是orecle公司于2014年3月发布,可以看成是`自Java 5 以来最具革命性的版本`。Java 8为Java语言、编译器、类库、开发工具与
Java基础知识强化(用于自我巩固)以及审查
1\. Java 和 JDK 的关系 ------------------ JDK(Java Development Kit)Java 开发工具包,它包括:编译器、Java 运行环境(JRE,Java Runtime Environment)、JVM(Java 虚拟机)监控和诊断工具等 Java 则表示一种开发语言。 2. Java 程序是怎么执行的?
Java学习路线(完整详细版)
 科技在进步,时代也在发展,很多人的理想再也不是骑马喝酒走四方,而是学习掌握java技术,真正的实现高薪就业,说到学习Java,我们今天就来说说Java开发需要学习的内容,说说java学习路线,说说Java学习的课程内容。   Java学习大致分为四大阶段,具体内容如下:   **第一阶段——Java基础**   Java的学习内容从计算机基本概念,D
Java环境设置JDK为例
**环境设置分三步:安装前准备;安装;环境设置及测试。** * * * 一、安装前准备 了解一下需要安装的都是什么:Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称。由James Gosling和同事们共同研发,并在1995年正式推出。且Java包括三个平台版本,分别为Java SE
Java的JDK到底是什么?Java JDK小白安装教程(win10)!
小白的Java JDK安装教程(win10): ----------------------- Java JDK安装3步走,手把手教小白安装Java JDK! * **Java JDK**是干什么用的?如何安装? * 安装完 Java JDK 为什么要设置**环境变量**? * 如何设置Java JDK 环境变量 一、Java JDK 是干
java 配置環境
JAVA\_HOME                                      C:\\Java\\jdk1.8.0\_11 PATH:                                                 %JAVA\_HOME%\\bin;%JAVA\_HOME%\\jre\\bin; CLASSPATH: 
java基础知识随身记
2018年11月12日20:51:35 一、基础知识: 1、JVM、JRE和JDK的区别: JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性。   java语言是跨平台,jvm不是跨平台的。 JRE(Java Runtime Environment):java的运行环境,包括jvm+java的核心类
java学习第一步
Java SE 磨刀不误砍柴工,工欲善其事必先利其器,咱们先搞好硬件配置,才能顺利的搞好Java学习 阶段一 1、认识Java 2、java发展史及用户 3、配置Java环境 4、JDK8下载安装 5、配置环境变量     JAVA\_HOME配置         java\_home配置两种方法:                 1、J
java的特性
java的特性 ======= 1、Java语言是简单的 2、Java语言是面向对象的 3、Java语言是分布式的 4、Java语言是健壮的 5、Java语言是安全的 6、Java语言是可移植性的 7、Java语言是解释型的 8、Java语言是多线程的 9、Java语言是动态的语言   **Java语言是简单的:**  
How to switch java version on ubuntu 20.04
* View all java versions lwk@qwfys:~$ update-java-alternatives --list java-1.11.0-openjdk-amd64 1111 /usr/lib/jvm/java-1.11.0-openjdk-amd64 java-1.8.0-ope
Jmeter使用过程中遇到的问题
**问题一:** 已安装java,也配置了java环境变量,但打开jmeter时提示:Not able to find Java executable or version. **解决方法:** 在jmeter.bat文件前面加上以上两句: SET JAVA\_HOME=C:\\Program Files\\Java\\jdk1.7.0\_51 S
Linux中查看jdk安装目录、Linux卸载jdk、rpm命令、rm命令参数
一、查看jdk安装目录 ----------- [root@node001 ~]# whereis java java: /usr/bin/java /usr/local/java #java执行路径 [root@node001 ~]# which java /usr/bin/java #查看JDK安装路径