Java Native Interface 五 JNI里的多線程與JNI方法的注冊


本文是《The Java Native Interface Programmer’s Guide and Specification》讀書筆記

JNI里的多線程

在本地方法里寫有關多線程的代碼時,需要知道下面幾個約束:

  1. 一個JNIEnv指針只在與它關聯的線程里有效,也就是說,在線程間傳遞JNIEnv指針和在多線程環境里通過緩存來使用它是不允許和不安全的。JVM在同一個線程里多次調用同一個本地方法時傳遞的是同一個JNIEnv指針,但在不同的線程里調用同一個本地方法時傳遞的是不同的JNIEnv指針。
  2. 本地引用只在創建它的線程里有效,也就是說你不能在線程間傳遞本地引用。因為在多線程的環境里可能會使用到相同的引用,因此我們需要將本地引用轉型為全局引用。

JNI里的同步機制(類似於鎖的獲取與釋放)

在本地方法里,可以通過JNI函數來實現Java里的同步塊(互斥資源的使用),用方法MonitorEnter來得到一個JNI引用的監控器(鎖),方法MonitorExit釋放監控器(鎖的釋放),下面是簡單的使用場景:

//省略了其他代碼,下面只是本地方法實現代碼里的某一部分
if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
    ... /* 獲取obj引用的鎖失敗,進行相應的處理 */
}
/* 編寫需要同步的代碼塊 */
  ....
if ((*env)->ExceptionOccurred(env)) {
    ... /* 異常處理的代碼  */
    /* 在這里要記得調用 MonitorExit來釋放所獲得的監控器  */
    if ((*env)->MonitorExit(env, obj) != JNI_OK) ...;
}
/*正常的調用MonitorExit來釋放鎖*/
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
    ... /* 釋放obj引用的鎖失敗,進行相應的處理 */
};

需要注意的是,在同步代碼塊里可能會發生異常,我們需要的對應的異常處理代碼中調用MonitorExit方法來釋放鎖,如果忘記調用這個釋放監控器的方法,可能會導致死鎖的發生。因為在JNI里使用同步機制會比較麻煩,因此我們盡可能在ava的程序里來實現相應的同步機制。

Java API中提供了一些對線程間同步非常有用的方法,如Object.wait,Object.notify,Object.notifyAll來等待獲取一個對象的鎖,喚醒等待獲得對象鎖的對象等。但在JNI里並沒有提供對應的方法來等待獲取對象的監控器,喚醒等待獲取對象監控器的對象,因此在JNI里通常采用JNI的方法調用機制來調用對應的Java方法來實現相應的操作。在下面的代碼里,我們假設已經獲得了相應方法的methodID緩存在全局引用中;

/* precomputed method IDs */
static jmethodID MID_Object_wait;
static jmethodID MID_Object_notify;
static jmethodID MID_Object_notifyAll;
void
JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout)
{
    (*env)->CallVoidMethod(env, object, MID_Object_wait,
                           timeout);
}
void
JNU_MonitorNotify(JNIEnv *env, jobject object)
{
    (*env)->CallVoidMethod(env, object, MID_Object_notify);
}
void
JNU_MonitorNotifyAll(JNIEnv *env, jobject object)
{
    (*env)->CallVoidMethod(env, object, MID_Object_notifyAll);
}

在任意上下文中獲取JNIEnv指針

在首頁的介紹中,我們知道JNIEnv指針只在與它相關聯的線程里是有效的,一般對於本地方法來說,他們是從JVM中得到這個指針作為方法的第一個參數的。但也有一小部分本地代碼不是從JVM中直接得到這個JNIEnv指針的,比如本地方法里的某一部分代碼是屬於操作系統調用的某一個方法的,因此這可能就導致將JNIEnv指針作為參數是沒有用的。這時我們就可以調用方法AttachCurrentThread來得到當前線程的JNIEnv指針。只要當前這個線程已經加載到JVM中,就可以返回正確的指針。

JavaVM *jvm; /* already set */
f()
{
    JNIEnv *env;
    (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
    ... /* use env */
}

上面的代碼里,需要先獲取一個JVM的指針,JNI里有許多方法可以得到一個JVM指針,JNI_GetCreatedJavaVMs,GetJavaVM等。並且JVM指針是可以保存在全局引用里的。

本地代碼的注冊

本地代碼的注冊方式有兩種:

  1. 在執行本地方法前,在Java代碼里使用語句System.loadLibrary("foo")加載本地方法所有的鏈接庫;
  2. 在本地方法的實現里,需要用到其他鏈接庫里的本地方法時,第一種方法就不適用了,比如,在本地方法里聲明另一個本地方法void JNICALL g_impl(JNIEnv *env, jobject self);但它的實現是在另一個鏈接庫里實現的,則需要使用下面的代碼來作為這個方法的實現:
//這里某一個本地方法里的代碼,省略了其他部分代碼
 JNINativeMethod nm;
nm.name = "g";//需要使用的其他鏈接庫里的本地方法的名字
/* 方法的描述,如返回值,參數等 */
nm.signature = "()V";
nm.fnPtr = g_impl;//在這里的本地方法的聲明的名字
//注冊g_impl方法
(*env)->RegisterNatives(env, cls, &nm, 1);

方法g_imlp的聲明並不需要遵循JNI的命名規范,因為這只是調用時的方法指針,並不需要展開代碼(調用這個方法時,實際調用的是另一個鏈接庫里名為g的JNI方法),所有不需要使用JNIEXPORT,但需要遵循JNI的調用規范。

使用動態注冊鏈接庫的方法的好處為:

其他

將本地代碼創建的string對象轉型為jstring對象返回給Java程序:

jstring JNU_NewStringNative(JNIEnv *env, const char *str)
{
    jstring result;
    jbyteArray bytes = 0;
    int len;
    if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
        return NULL; /* out of memory error */
    }
    len = strlen(str);
    bytes = (*env)->NewByteArray(env, len);
    if (bytes != NULL) {
        (*env)->SetByteArrayRegion(env, bytes, 0, len,
                                   (jbyte *)str);
result = (*env)->NewObject(env, Class_java_lang_String,
                                   MID_String_init, bytes);
        (*env)->DeleteLocalRef(env, bytes);
        return result;
    } /* else fall through */
    return NULL;
}

將jstring對象轉型為本地string對象

char *JNU_GetStringNativeChars(JNIEnv *env, jstring jstr)
{
    jbyteArray bytes = 0;
    jthrowable exc;
    char *result = 0;
    if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
        return 0; /* out of memory error */
    }
    bytes = (*env)->CallObjectMethod(env, jstr,
                                     MID_String_getBytes);
    exc = (*env)->ExceptionOccurred(env);
    if (!exc) {
        jint len = (*env)->GetArrayLength(env, bytes);
        result = (char *)malloc(len + 1);
        if (result == 0) {
            JNU_ThrowByName(env, "java/lang/OutOfMemoryError",
                            0);
            (*env)->DeleteLocalRef(env, bytes);
            return 0;
        }
        (*env)->GetByteArrayRegion(env, bytes, 0, len,
                                   (jbyte *)result);
        result[len] = 0; /* NULL-terminate */
    } else {
        (*env)->DeleteLocalRef(env, exc);
    }
    (*env)->DeleteLocalRef(env, bytes);
    return result;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM