在我的应用程序中,我有一些本机代码的包装器,通过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
。我无法理解这些案件是什么...... 注意:
赏金编辑:重新访问原生代码的方法/建议很明确,请不要在回复中提供。我需要纯Java解决方案/解决方法。
答案 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应用程序来处理调用,跟踪和终止这些线程。如果您的许可协议提供任何类型的支持,最好让第三方为您做这件事。
答案 4 :(得分:0)
我不会重复都铎给出的所有宝贵建议...... 我将添加一个替代的架构点,同时使用任何排队机制来处理主Java应用程序和启动的本机线程之间的通信.... 此线程可能是代理的客户端,如果发生某些特殊事件(终止)并因此而执行操作(停止长时间运行的作业),则会通知该线程 当然这会增加一些复杂性,但却是一个非常优雅的解决方案 当然,如果本机线程不健壮,它将不会改变整体的健壮性。 处理本机线程和代理之间通信的一种方法是使用类似STOMP的接口(许多代理Apache activemq,来自Oracle的MQ公开这样的接口)......
HTH 杰罗姆