终止正在运行本机代码的线程

时间:2012-01-06 17:53:28

标签: java multithreading

在我的应用程序中,我有一些本机代码的包装器,通过JNI桥调用。此本机代码需要在单独的线程中执行(并行处理)。但问题是代码有时会“挂起”,因此线程需要“强制”终止。不幸的是我没有找到任何“微妙”的方法:一般建议是告诉线程中的代码优雅地退出,但我不能用这个本机代码(这是上面的第三方代码)。

我使用Java Concurrent API进行任务提交:

Future<Integer> processFuture = taskExecutor.submit(callable);

try {
    result = processFuture.get(this.executionTimeout, TimeUnit.SECONDS).intValue();
}
catch (TimeoutException e) {
    // How to kill the thread here?
    throw new ExecutionTimeoutException("Execution timed out (max " + this.executionTimeout / 60 + "min)");
}
catch (...) {
    ... exception handling for other cases
}

Future#cancel()只会中断线程,但不会终止它。所以我使用了以下技巧:

class DestroyableCallable implements Callable<Integer> {

    private Thread  workerThread;

    @Override
    public Integer call() {
        workerThread = Thread.currentThread();

        return Integer.valueOf(JniBridge.process(...));
    }

    public void stopWorkerThread() {
        if (workerThread != null) {
            workerThread.stop();
        }
    }
}

DestroyableCallable callable = new DestroyableCallable();

Future<Integer> processFuture = taskExecutor.submit(callable);

try {
    result = processFuture.get(this.executionTimeout, TimeUnit.SECONDS).intValue();
}
catch (TimeoutException e) {
    processFuture.cancel(true);
    // Dirty:
    callable.stopWorkerThread();

    ThreadPoolTaskExecutor threadPoolTaskExecutor = (ThreadPoolTaskExecutor) taskExecutor;

    logger.debug("poolSize: " + threadPoolTaskExecutor.getPoolSize() + ", maxPoolSize:"
                    + threadPoolTaskExecutor.getMaxPoolSize() + ", activeCount:"
                    + threadPoolTaskExecutor.getActiveCount());
    }

    throw new ...;
}
catch (...) {
    ... exception handling for other cases
}

此代码的问题/问题:

  • 一般来说这是正确的方法吗?还有其他更优雅的选择吗?
  • 任务执行者的
  • activeCount没有减少,因此任务执行者仍“认为”该线程正在运行
  • 我必须将workerThread != null检查添加到stopWorkerThread()方法,因为在某些情况下此变量原来是null。我无法理解这些案件是什么......

注意:

  • 本机代码不使用文件描述符(套接字)。一切都作为数据块传递给它,并以相同的方式返回。
  • 本机代码是CPU密集型的。即使它保证终止,也可能需要很长时间。

赏金编辑:重新访问原生代码的方法/建议很明确,请不要在回复中提供。我需要纯Java解决方案/解决方法。

5 个答案:

答案 0 :(得分:9)

Java有强制线程终止的纯选项。它是古老的,不推荐使用Thread.stop()(AFAIK)。 安全线程终止的 no 选项(不推荐使用.stop()的原因是什么,甚至不允许JVM实现者实现)。

原因是app 中的所有线程共享内存和资源 - 所以,如果你强制终止线程在某个任意点,你无法证明什么终止线程没有离开一些共享内存/资源处于不一致状态。你甚至不能(通常)假设哪些资源(可能)是脏的(因为你不知道线程被停止的确切位置)。

因此,如果您希望应用程序的某些线程能够中断,唯一的解决方案是在设计阶段提供“保存点”的一些表示法 - 目标线程代码中的位置,保证不要改变共享状态,所以线程在这里退出是安全的。而且正是Thread.stop()javadocs告诉你的:安全地中断线程的唯一方法是设计线程的代码,这样它就可以自己响应某种中断请求。某种标志,由线程不时检查。

我试图告诉你:你不能做你被问及使用java线程/并发的事情。我建议你的方式(这是早在这里给出的)是在不同的过程中完成你的工作。强制终止进程比线程安全得多,因为1)进程彼此分离得多,并且2)OS在进程终止后负责许多清理。杀戮过程并不是完全安全的,因为存在某种资源(例如文件),默认情况下不会被操作系统清除,但在您的情况下它似乎是安全的。

所以你设计了一个小的独立应用程序(甚至可能在java中 - 如果你的第三方lib没有提供其他绑定,甚至在shell脚本中),只有工作是让你计算。你从主应用程序开始这样的过程,给它工作,并启动看门狗。它看门狗检测到超时 - 它强行杀死进程。

这是唯一的解决方案草案。你可以实现某种进程池,如果你想提高性能(启动过程可能需要时间),等等......

答案 1 :(得分:2)

绝对是你在这里的一个丑陋的黑客...

首先,线程池线程不应单独进行调整,通常应保持运行直到完成,尤其是Thread.stop()不会停止,即使对于普通线程也不建议这样做。

正如我所说,Thread.stop()的使用从未被鼓励过,并且通常会使线程处于不一致状态,这可能是线程池未将线程视为“死”的原因。它甚至可能都不会杀掉它。

知道本机代码挂起的原因吗?我认为问题的根源在这里,而不是线程停止部分。线程通常应该尽可能地运行直到完成。也许你可以找到一个正常工作的更好的实现(或者如果你编写它就实现不同的东西)。

编辑:对于第3点,您可能需要将对当前线程的引用声明为volatile,因为您要在一个线程中分配它并在另一个线程中读取它:

private volatile Thread workerThread;

编辑2 :我开始认为你的JNI代码只进行数值计算,并且如果线程突然被杀死,则不会打开任何可能保持不一致状态的句柄。你能证实一下吗?

在这种情况下,请允许我反对我自己的建议并告诉您,在这种情况下,您可以使用Thread.stop()安全地杀死该帖子。但是,我建议您使用单独的线程而不是线程池线程,以避免线程池处于不一致状态(正如您所提到的,它不会将线程视为死亡)。它也更实用,因为你不需要做所有这些技巧来让线程自行停止,因为你可以直接在主线程上调用stop(),这与线程池线程不同。

答案 2 :(得分:2)

您可以将对JNI方法的单个调用封装到单独的Java应用程序中,然后使用java.lang.Process分叉另一个Java进程。然后,您可以调用Process.destroy()来破坏操作系统级别的该进程。

根据您的环境和其他注意事项,您可能需要做一些技巧来了解如何查找java可执行文件,特别是如果您正在构建可以在不同平台上运行的可再发行软件。另一个问题是IPC,但可以使用Process的输入/输出流完成。

答案 3 :(得分:1)

由于您正在处理第三方代码,我建议您创建一个本机shell应用程序来处理调用,跟踪和终止这些线程。如果您的许可协议提供任何类型的支持,最好让第三方为您做这件事。

http://java.sun.com/docs/books/jni/html/other.html

答案 4 :(得分:0)

我不会重复都铎给出的所有宝贵建议...... 我将添加一个替代的架构点,同时使用任何排队机制来处理主Java应用程序和启动的本机线程之间的通信.... 此线程可能是代理的客户端,如果发生某些特殊事件(终止)并因此而执行操作(停止长时间运行的作业),则会通知该线程 当然这会增加一些复杂性,但却是一个非常优雅的解决方案 当然,如果本机线程不健壮,它将不会改变整体的健壮性。 处理本机线程和代理之间通信的一种方法是使用类似STOMP的接口(许多代理Apache activemq,来自Oracle的MQ公开这样的接口)......

HTH 杰罗姆