终止线程或异步任务的执行

5

假设我使用了IBM创建的一个jar文件。假设这个Jar文件有一个我需要的函数,但它最终构建的形式如下:

 while (true) {
     System.out.println(1)
 }

(当然,它并不是真的只打印1,但为了举例子,我们就这么说吧) 因此,我使用future在另一个线程中调用执行该操作的函数。如何完全终止运行此代码的线程?或者,如何终止在Kotlin中运行代码的异步任务?
在Kotlin或Java中的解决方案将非常好,提前感谢!
编辑:
我已经发现,如果这是一个线程,我可以使用Thread#stop()来彻底停止它。但不幸的是,使构造函数多次抛出异常会导致JVM从内存中删除该类,在下一次实例化该类时会引发NoClassDefFoundError。

你有更改 while (true) 条件的方法吗? - Amit Bera
你说过你有“future”。调用Future#cancel(true)会有任何问题吗? - rkosegi
@AmitBera 不行,我无法触碰执行此操作的函数内部代码。 - Antil Karev
@rkosegi 我确实调用了它,甚至调用了ExecutorService#shutdownNow()函数,但两者都无法终止线程。 - Antil Karev
1
Thread#stop() 是你唯一的选择,但是 a) 它是一个糟糕、易泄漏的机制,可能会破坏整个应用程序,b) 仍然可能无法杀死线程,因为它只是在线程中抛出异常。 - Marko Topolnik
4个回答

1
如果您能捕获它的线程,只要它在内部执行某种阻塞功能,您就应该能够杀死它。
class OtherFunction implements Runnable {

    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // We assume the thread will exit when interrupted.
                System.out.println("Bye!!");
                return;
            }
            System.out.println("Hello");
        }
    }
}


class Killable implements Runnable {
    final Runnable target;
    private Thread itsThread;

    Killable(Runnable target) {
        this.target = target;
    }

    @Override
    public void run() {
        // Catch the thread id of the target.
        itsThread = Thread.currentThread();
        // Launch it.
        target.run();
    }

    public void kill() {
        // Give it a good kick.
        itsThread.interrupt();
    }
}

public void test() throws InterruptedException {
    OtherFunction theFunction = new OtherFunction();
    Killable killableVersion = new Killable(theFunction);
    new Thread(killableVersion).start();
    // Wait for a few seconds.
    Thread.sleep(10000);
    // Kill it.
    killableVersion.kill();
}

4
我不确定这会不会对问题的提出者有所帮助:他调用的方法似乎是不可中断的。在问题的评论中,提出者提到了调用future.cancel(true)executor.shutdownNow()两个方法,它们都试图通过中断线程来取消运行的任务,但任务仍在执行。 - Slaw
@MarkoTopolnik,“Thread#stop()”方法已被废弃,且不再起作用。 - DiLDoST
@DiLDoST Thread#stop()自1997年以来已被弃用 :) 几乎没有人有机会在其非弃用状态下调用此方法。 - Marko Topolnik
@MarkoTopolnik 我说了不同的话吗?希望没有。 - DiLDoST
@DiLDoST 是的,你说“它不再有帮助了”,这让人觉得好像Thread.stop()最近发生了一些变化。然而,整个线程都假定这是默认知识,即它已经被弃用了。 - Marko Topolnik
显示剩余4条评论

0

Thread#stop()已被弃用,因为它是“固有不安全的”,如果可能的话应该避免使用。

它是不稳定和容易出错的源头,而且可能会失败!

它实际上会导致目标线程抛出一个ThreadDeath异常。无论如何,代码作者都不太可能预料到这种结果。对象可能处于不一致的状态,外部资源可能被占用并泄漏,文件可能只写了一部分。

有处理意外错误的方法,但实际上大多数代码都是假设它知道可能抛出哪些异常而编写的,并没有预料到这样的“惊喜”。

由于ThreadDeath是一个Throwable,任何catch(Throwable t)都可以捕获它,除非在线程执行的每个代码片段中都非常小心(不现实),否则ThreadDeath可能会被吸收而不结束线程。

正确的处理方式是声明一个原子变量(通常作为表示任务的Runnable的一部分)。

AtomicBoolean stopThread=new AtomicBoolean(false);

然后将循环写成:

 while (!stopThread.get()) {
     System.out.println(1);
 }

并提供一个方法:

public void stopThread(){
    stopThread.set(true);
} 

或者你可以使用 interrupt() 并检查 interrupted()。这些是在 Thread 类中提供的更简洁的方法。当调用 interrupted() 时,它会清除标志位。但这并不总是有帮助的,虽然可以通过 Thread.currentThread().isInterrupted() 来检查标志位,但“检查标志位会清除标志位”的行为可能会导致一些未被预期的异常抛出,类似于 stop() 方法存在的一些问题。正确的方法是使用自己的标志位,并完全控制进程何时决定退出。

随你选择。

另请参阅:Java 线程原语废弃


你是否曾经想过为什么在某些并发进程上点击“取消”后,你经常需要等待很长时间才能得到响应?这就是原因。任务需要达到一个明确定义的点,并进行任何必要的清理以以明确定义的方式终止。

Thread#stop()方法看作是通过踢下车来停止骑自行车的人。该方法向他们挥舞红旗,然后他们会尽可能安全地停下来。

Thread#stop()从未应该出现在Java中,你也永远不应该使用它。你可以在开发和小型系统中使用它,但在大型生产环境中会造成混乱。它不仅被弃用为“不推荐使用”,而且是“固有不安全”的,请勿使用它。它已经被弃用多年了,令人失望的是从未公布过任何“删除日期”。

以下是一个示例,根据您选择的危险程度,使用Thread#stop()interrupt()方法。

import java.lang.System;
import java.lang.Thread;

class Ideone
{

    private static boolean beDangerous=true;//Indicates if we're going to use the Thread#stop().... 

    //This main method uses either stop() or interrupt() depending on the option.
    public static void main (String[] args) throws java.lang.Exception
    {

        PrimeFactor factor=new PrimeFactor();
        try{
    for(int i=1;i<30;++i){
        Thread thrd=new Thread(new Primer(factor));
        thrd.start();

        Thread.sleep(10);//Represents some concurrent processing...

        if(beDangerous){
            thrd.stop();
        }else{
            thrd.interrupt();
        }
        thrd.join();
        if(!factor.check()){
            System.out.println("Oops at "+i);
        }
    }
        }catch(Throwable t){
            System.out.println(t);
        }
    }

    //This class just hammers the PrimeFactor object until interrupt()ed (or stop()ed).
    private static class Primer implements Runnable {

        private PrimeFactor factor;

        public Primer(PrimeFactor ifactor){
            factor=ifactor;
        }


        public void run(){
            int i=1;
            while(!Thread.interrupted()){   
                factor.set(i++);    
        }
           }
    }

    //Don't worry about this bit too much.
    //It's a class that does a non-trivial calculation and that's all we need to know.
    //"You're not expected to understand this". If you don't get the joke, Google it.

    //This class calculates the largest prime factor of some given number.
    //Here it represents a class that ensures internal consistency using synchronized.
    //But if we use Thread#stop() this apprently thread-safe class will be broken.
    private static class PrimeFactor {
        private long num;
        private long prime;

        public static long getFactor(long num){
            if(num<=1){
               return num;
            }
            long temp=num;
            long factor=2;
            for(int i=2;temp!=1;++i){
                if(temp%i==0){
                    factor=i;
                    do{
                        temp=temp/i;
                    }while(temp%i==0);
                }   
            }
            return factor;
        } 

        public synchronized void set(long value){
            num=value;
            prime=getFactor(value);
        }

        public synchronized boolean check(){
            return prime==getFactor(num);
        }
    }   
}

典型的部分输出:

Oops at 1
Oops at 2
Oops at 3
Oops at 6
Oops at 8

请注意,PrimeFactor类可以被描述为线程安全的。所有它的方法都是synchronized的。想象一下它在某个库中。期望“线程安全”意味着Thread#stop()安全是不现实的,唯一的方法是很突兀。将其调用放入try-catch(ThreadDeath tde)块中并不能解决任何问题。损坏将在捕获之前就已经发生了。
不要让自己相信将set()更改为以下内容即可解决问题:
public synchronized void set(long value){
    long temp=getFactor(value);
    num=value;
    prime=temp;
}

首先,执行任务期间可能会抛出“ThreadDeath”异常,所以这只是增加了竞争条件发生的几率。它并没有被否定。永远不要关于竞争条件发生的可能性进行讨论。“程序调用方法的次数多达数十亿次,因此每次都有可能发生一次千载难逢的情况。”
如果使用“Thread#stop()”,你基本上不能使用任何库对象,包括“java.*”,并且需要费尽周折地处理代码中的每个位置可能出现的“ThreadDeath”异常,但几乎肯定最终还是会失败。

1
正如我之前所述,如果执行 while 循环的函数对我可访问,那么这可能会有所帮助。不幸的是,我无法编辑此函数。因此我只能终止该线程。 - Antil Karev
只要你知道你的代码是不安全的,而且无法变得安全,那么答案就是——找出框架如何让你干净地停止它,或者不使用该框架。没有可信的第三方软件会强迫你以这种方式做事。在某些用户论坛上提出问题或与原始作者交谈。只是不要认为你已经修复了任何东西。它只是以不同和不可预测的方式破裂了。在多线程中,通常情况下这是一个更糟糕的地方。我知道你说你不能碰它。听起来像是你不能修复它。 - Persixty
@AntilKarev,“IBM创建的'jar'是什么?”这不符合他们通常的标准,把那样的垃圾丢给你。我希望你错过了正确答案,如果没有,你就不要为此付钱。 - Persixty
好的,这是IBM MQ客户端jar包。不幸的是,它已经嵌入到我工作的公司中,我们无法转移到JMS,因此目前我需要找到一个解决方案。 - Antil Karev
@AntilKarev MQ是旗舰级的专业质量软件。我不熟悉它的API,但如果答案似乎是Thread#stop(),那么你或你的公司一定是在错误地使用它,或者你的公司已经包含了一种正确停止它的方法,只是你还没有被告知或找到它。IBM SE绝不会认为thread.stop();是有效的代码,并且不会强制你使用它。 - Persixty
当实例化一个新的MQQueueManager并遇到网络问题时,可能会导致线程卡住...我可以设置一些超时时间,但不幸的是,这个配置是全局设置的,适用于机器上运行的每个应用程序,这会导致机器上两个可能运行的应用程序都有相同的超时时间。但我需要一个运行时可配置的超时时间。因此,API的支持并不是很好,实际上,即使我使用Thread#stop(),它最终也会被JVM从内存中擦除。 - Antil Karev

0

在Java中没有官方的线程终止方式。这是一个缺陷。(无需在此与之争论)Thread#stop()不应该被弃用。可以改进它,使其无法使用。即使现在,它大多数时候也能正常工作。

现在,如果我编写需要kill操作的函数,我会启动新线程并将其连接到具有超时或其他断开机制的线程。这将使您的代码继续运行,就像主线程已经被杀死一样。问题是主线程仍在运行。所有资源仍在使用中。这仍然比应用程序冻结要好。调用thread.interrupt()是第一步,但是如果这不起作用,则使用thread.stop()是足够的。它不会让事情变得更糟。

如果您真的必须终止线程,唯一的方法就是通过jni启动另一个jvm,在那里运行不安全的代码,并使用Linux kill -9停止整个进程(如有需要)。

我相信终止线程是完全可能的,只是jvm开发人员没有足够重视。我经常遇到这种情况,而像“不使用任何库”,“修复所有外部代码”,“编写自己的语言”或“忍受它”等答案只会让人感到沮丧。


我感同身受 - DiLDoST

0

看起来Thread#stop()解决了我的问题。我知道它已经过时,可以通过catch(Throwable t)来防止,但至少对我有用。

顺便说一下,为了从执行器中获取线程,我使用了AtomicReference<Thread>并在回调中设置了它。


不要使用Thread#stop()。实际上,几乎所有使用它的程序都是无效的。它在添加后的发布版本中被弃用,因为它本来就不应该存在。请参见我的答案。 - Persixty
我不确定你所说的“类的擦除”是什么意思,除非你的意思是在构造函数中抛出异常会导致未返回完全构造的对象。这是正确的行为。你没有提供足够关于你的用例的信息来给予建议。 - Persixty
这意味着下一次我尝试实例化MQQueueManager类的新实例时,会导致抛出NoClassDefError。 - Antil Karev
你想要缩短或者延长超时时间。编写一个Runnable类,它实际上会尝试(甚至不断地尝试)去实例化该类并在一个平行线程中运行它。这有什么帮助呢?Runnable控制一个正在运行/成功/失败的标志,并使用this.notify()来指示状态的改变。主线程使用wait(long timeout)用您喜欢的超时时间等待通知。如果超时,您可以在Runnable中设置一个AtomicBoolean,告诉构造函数进行'清理'。这绝对安全,而且您完全掌控了主线程挂起的时间长度。 - Persixty
缺点是即使你已经放弃等待,另一个线程仍将继续运行并消耗资源直到超时结束。重要提示:为了生成正确的程序,您必须最终使用join()加入其他线程,以确保它真正干净地完成并可能吸收最后一点延迟。因为MQ正在处理系统范围的资源,所以当您的main()结束时,您不能冒险让JVM本身杀死线程,否则可能会导致应用程序的不同部分遇到相同的问题。如果您的线程不是main(),请在run()方法的末尾执行此操作。 - Persixty
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接