在Android中,如何缓存JNI对象并保证线程安全性。

8
我正在使用本地线程(pthreads)编写C++应用程序,并需要调用一些Java方法等。我不确定哪些JNI对象可以安全缓存,即存储在我的C++对象中以供稍后使用,可能/很可能由不同的线程使用。我知道如果我的类的方法可以被不同的线程调用,我不能缓存JNIEnv,而是要缓存JavaVM并通过附加当前线程来获取JNIEnv。但这是否意味着我不能缓存从JNIEnv获得的任何内容?我需要使用以下JNIEnv方法获得的对象:
FindClass、GetMethodID、NewObject、NewGlobalRef
这些对象在线程之间保持有效,还是每次都必须获取新的对象?如果是后者,有没有一种方法可以在一个本地线程中创建对象并能够在不同的线程中访问相同的对象?
2个回答

10

JNI 方法,如 FindClassGetMethodIDGetFieldID 是昂贵的操作,保证在 JVM 的生命周期内生成相同的结果。由于这些操作很耗时,因此明智的做法是将结果存储在某个地方以便稍后在本机侧重用(这就是缓存)。

JNI 缓存仅涉及这些 JNI 函数调用。如果您想缓存任何其他 C++ 或 Java 对象,这是一个不同的话题。(只是为了清楚起见)。

缓存的类、方法和字段不依赖于它们被检索的线程,因此它们在不同的线程之间是有效的。最多,在使用 Set<type>FieldGet<type>Field 获取或设置某个对象的字段时,您必须执行线程安全操作。

由于 FindClass 返回对 class 对象的本地引用,因此您必须将其转换为全局引用,以确保在检索它的函数结束后重复使用它。您可以通过使用 NewGlobalReference 来实现这一点:

jclass tmp_double_Class = env->FindClass( "java/lang/Double" ); // Check for exceptions!
double_Class = static_cast<jclass>( env->NewGlobalRef( tmp_double_Class ) );
if( double_Class == NULL )
  return;
env->DeleteLocalRef( tmp_double_Class );

这里有一个JNI缓存话题的例子:

MyJni.cpp:

// Just a shortcut for checking for exceptions
#define CHECK_JNI_EXCEPTION( JNIenv ) \
  if( JNIenv->ExceptionCheck() )\
  {\
    JNIenv->ExceptionClear();\
    return JNI_FALSE;\
  }\
\

// Global variables
jclass    point_Class;
jmethodID point_ctor_Method;
jfieldID  point_x_Field;
jfieldID  point_y_Field;

JNIEXPORT jboolean JNICALL Java_com_company_package_MyClass_nativeInit( JNIEnv * env,
                                                                        jclass   clazz )
{
  // Cache java.lang.Double class, methods and fields
  jclass tmp_point_Class = env->FindClass( "android/graphics/Point" );
  CHECK_JNI_EXCEPTION( env )
  point_Class = static_cast<jclass>( env->NewGlobalRef( tmp_point_Class ) );
  if( point_Class == NULL )
    return JNI_FALSE;
  env->DeleteLocalRef( tmp_point_Class );
  point_ctor_Method = env->GetMethodID( point_Class, "<init>", "(II)V" );
  CHECK_JNI_EXCEPTION( env )
  point_x_Field = env->GetFieldID( point_Class, "x", "I" );
  CHECK_JNI_EXCEPTION( env )
  point_y_Field = env->GetFieldID( point_Class, "y", "I" );
  CHECK_JNI_EXCEPTION( env )
  return JNI_TRUE;
}

MyJni.java:

package com.company.package;

class MyClass {
  // ... All java code here ... 

  // Trigger JNI Caching (could be also done using JNI_OnLoad...)
  private static native void nativeInit();

  static {
    System.loadLibrary( "mylib" );
    nativeInit(); // should check the result
  }
}

玩得愉快 ;)


4

对象不是线程特定的。它们最初是“本地”引用,如果你想保留一份副本,你必须通过创建(和最终删除)一个“全局”引用告诉VM你正在这样做。

请参见http://developer.android.com/training/articles/perf-jni.html,尤其是“本地和全局引用”部分。


谢谢。那个页面对于这种事情比Oracle的JNI规范更具信息量。 - realh
全局变量的赋值不应该在锁定状态下进行吗?我几乎找不到有关JNI线程安全的文档或示例。 - Malcolm
1
如果这些值是由一个线程设置并由另一个线程读取的,那么除非您可以保证读取线程在值被写入后启动(并且一旦写入,这些值就不会被更新),否则应该通过互斥锁来保护这些值。许多应用程序通过尽早加载JNI库并在JNI_OnLoad中进行设置来实现此目的,在其他线程启动之前完成设置。有关此主题的深入了解,请参阅:http://developer.android.com/training/articles/smp.html - fadden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接