美文网首页
如何正确停止Java线程,终止Java线程的三种方法

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

作者: Zephyr006 | 来源:发表于2020-08-12 16:46 被阅读0次

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

【原创,转载请注明出处】
我的 【博客园主页】 【CSDN主页】 【简书主页】
加V进Java交流群,备注Java交流:w1129574379

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

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

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

停止一个线程的推荐做法

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

例如,假设您的程序包含以下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();
    }

相关文章

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

    如何正确停止Java线程,终止Java线程的三种方法 【原创,转载请注明出处】我的 【博客园主页】 【CSD...

  • 一个简单的带中断策略的Thread用例

    使用: 如何优雅的"停止"一个正在运行的JAVA线程如何正确地停止一个线程?关闭线程的正确方法:“优雅”的中断Ja...

  • 停止及中断运行的线程

    停止线程的方式 在 Java 中停止线程的方式有三种:1、线程的 run 方法执行到最后,线程正常结束并退出;2、...

  • JAVA终止线程的方法

    如何正确地停止一个线程 方法如下: 使用退出标志,使线程正常退出,也就是当 run 方法完成后线程终止 使用 in...

  • 如何正确的中断线程?你的姿势是否正确

    Java停止线程的逻辑(协同、通知) 在Java程序中,我们想要停止一个线程可以通过interrupt方法进行停止...

  • 多线程之中断线程

    1、 java中没有可以强制线程终止的方法。但是存在interrupt方法用来请求终止线程。当对一个线程调用int...

  • java中如何终止线程的执行

    java中如何终止线程的执行 线程被创建后,当run()方法执行完毕,线程便会正常结束和销毁。但是,在有些情况下,...

  • Java多线程和Executors框架

    Java多线程 正确的编程方法:首先使代码正确运行,然后再提高代码的速度。 常见创建线程的三种方式: Thread...

  • Java线程系列——线程的停止

    一、如何正确的停止线程 线程的停止有两种情况,一种是程序运行完毕,线程自然停止;而另一种是用户主动取消。Java语...

  • 线程的停止

    在Java中有以下3中方法可以终止正在运行的线程: 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止...

网友评论

      本文标题:如何正确停止Java线程,终止Java线程的三种方法

      本文链接:https://www.haomeiwen.com/subject/abssrktx.html