将耗时的JNI任务作为线程调用

时间:2009-07-21 07:59:34

标签: multithreading java-native-interface

我在使用线程中的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必须有一些东西。我根本不知道的主题。

感谢任何提示!

4 个答案:

答案 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,我的建议是不要。它可能会给你带来稳定性问题。以下是一些可能的替代方案:

  1. 使用Java重新编写本机库。
  2. 在C / C ++ /中编写本机库的包装器命令,并使用java.lang.Process和朋友运行它
  3. 将本机库转换为守护程序并使用套接字访问它。

答案 2 :(得分:1)

虽然你得到了答案,但我认为没有提供太多可能的根本原因。这里有几种可能性,但还有其他可能性。请注意,这些适用于Windows。

有一个涉及公寓线程的COM对象。公寓线程COM对象是VB可以创建的唯一类型,只能在创建它们的线程上使用。

安全功能,如模拟,通常是线程隔离的。如果初始化代码修改了线程的上下文,那么期望上下文到位的未来调用将失败。

特定于线程的内存存储是某些应用程序中支持多线程的技术(Java也有这样的功能)。

答案 3 :(得分:1)

这里有一个很好的文档:第8.1节JNI和线程。 http://java.sun.com/docs/books/jni/download/jni.pdf