我在使用线程中的JNI调用本机函数时遇到了一个棘手的问题。
本机函数是执行计算密集型任务的遗留代码。由于我不想冻结程序的其余部分,因此应该在后台线程中执行计算。 EventBus用于将计算结果发送回主程序。
基本上它应该非常简单,如下所示:
public class CalculationEngine {
private CalculationEngine(){}
public static void calculateInBackground(final Parameters parameters) {
new Thread(new Runnable() {
public void run() {
// Someone might change the parameters while our thread is running, so:
final Parameters clonedParameters = parameters.clone();
Results results = new Results();
natCalc(clonedParameters, results);
EventBus.publish("Results", results);
}
}).start();
}
public static void calculateNormally(final Parameters parameters) {
Results results = new Results();
natCalc(parameters, results);
EventBus.publish("Results", results);
}
private static native synchronized void
natCalc(Parameters parameters, Results results);
}
现在,阻止主程序的calculateNormally
方法工作正常,但calculateInBackground
方法只构造后台线程来做同样的事情,导致本机代码中的各种崩溃连续调用时。连续地,我的意思是它只在前一个线程完成后再次调用并返回结果。请注意,本机代码标记为synchronized
,以确保一次只能运行一个实例。
我的问题是,本机代码的行为方式可能会有所不同,具体取决于它是从主线程调用还是从其他线程调用?这就像本机代码保持“状态”,而不是真正退出,当它从主线程以外的线程中调用时。有没有办法在线程完成后“清理”或“刷新”? JNI和JNI必须有一些东西。我根本不知道的主题。
感谢任何提示!
答案 0 :(得分:7)
在谷歌搜索并找到短语"I've found JNI to be very buggy when called from seperate threads... So make sure only one thread ever calls your native code!"之后,我找到了一个有效的解决方案。这似乎是真的;解决方案是保持持久的“可重用”线程 - 我使用Executors.newSingleThreadExecutor()
- 并仅从该线程调用本机代码。它有效。
因此,与JNI的观点不同之处在于主线程与其他线程之间的差异,而是在连续调用中使用不同的线程。请注意,在有问题的代码中,每次都构造一个新线程。它应该以这种方式工作,但事实并非如此。 (不,我不是在缓存JNIEnv指针。)
无论是JNI错误,本机代码中的错误,它们与操作系统之间的交互还是其他什么,都会很有趣。但有时候你根本没有机会详细调试10000多行现有代码,但是,你很高兴能让它运行起来。这是示例代码的工作版本,我们称之为解决方法:
public class CalculationEngine {
private CalculationEngine(){}
private static Parameters parameters;
private static ExecutorService executor = Executors.newSingleThreadExecutor();
private static Runnable analysis = new Runnable() {
public synchronized void run() {
Results results = new Results();
natCalc(parameters, results);
EventBus.publish("Results", results);
}
};
public static synchronized void
calculateInBackground(final Parameters parameters) {
CalculationEngine.parameters = parameters.clone();
executor.submit(analysis);
}
private static native synchronized void
natCalc(Parameters parameters, Results results);
}
答案 1 :(得分:3)
如果您可以避免使用JNI,我的建议是不要。它可能会给你带来稳定性问题。以下是一些可能的替代方案:
答案 2 :(得分:1)
虽然你得到了答案,但我认为没有提供太多可能的根本原因。这里有几种可能性,但还有其他可能性。请注意,这些适用于Windows。
有一个涉及公寓线程的COM对象。公寓线程COM对象是VB可以创建的唯一类型,只能在创建它们的线程上使用。
安全功能,如模拟,通常是线程隔离的。如果初始化代码修改了线程的上下文,那么期望上下文到位的未来调用将失败。
特定于线程的内存存储是某些应用程序中支持多线程的技术(Java也有这样的功能)。
答案 3 :(得分:1)
这里有一个很好的文档:第8.1节JNI和线程。 http://java.sun.com/docs/books/jni/download/jni.pdf