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

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

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

Future 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 { 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 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 check添加到stopWorkerThread()方法,因为在某些情况下这个变量结果为null 。 我无法理解这些案件是什么……

笔记:

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

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

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

原因是应用程序内部的所有线程共享内存和资源 – 因此,如果您在某个任意点强制终止线程,则无法确定终止线程未将某些共享内存/资源置于不一致状态。 而且你甚至不能(通常)假设哪些资源(可能)是脏的(因为你不确切知道线程停止的哪一点)。

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

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

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

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

您可以将对JNI方法的单个调用包装到单独的Java应用程序中,然后使用java.lang.Process分叉另一个java进程。 然后,您可以调用Process.destroy()来在OS级别上销毁该进程。

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

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

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

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

首先,线程池线程并不意味着单独调整,通常应该保持运行直到完成,特别是不要使用Thread.stop()停止,即使对于普通线程也不建议。

正如我所说的那样, Thread.stop()的使用永远不会被鼓励,并且通常会使线程处于不一致状态,这可能是线程池没有看到线程“死”的原因。 它甚至可能都没有杀死它。

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

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

 private volatile Thread workerThread; 

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

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

我不会重复Tudor提供的所有宝贵意见….我将添加一个替代的架构点,同时使用任何排队机制来处理主Java应用程序和本机线程之间的通信….这个线程可能是代理的客户端,如果发生某些特殊事件(终止)并通知其行为(终止长时间运行的作业),则会收到通知。当然,这会增加一些复杂性,但这是一个非常优雅的解决方案。当然,如果本机线程是不强大它不会改变任何东西到整体的健壮性。处理本机线程和代理之间的通信的一种方法是使用类似STOMP的接口(许多代理Apache activemq,来自Oracle的MQ暴露这样的接口)……

HTH杰罗姆