JNI編程指南-第八章 多彩的JNI招數

第八章 多彩的JNI招數

 

我們已經討論了JNI在寫本地代碼和向本地應用程序中集成JVM時的特徵。本章接下來的部分分介紹其它的JNI特徵。

 

8.1 JNI和線程

 

JVM可以做到在相同的地址空間內執行多個線程。由於多個線程可能會在同時共享資源,所以,增加了程序的複雜性。

要完全理解本章的東西,你需要對多線程編程比較熟悉,知道怎麼樣在JAVA中用多線程訪問共享資源。

 

8.1.1 約束限制

 

如果你的本地代碼要運行在多個線程中,有一些約束條件需要注意,這樣的話,才能使得你的本地代碼無論被多少個線程同時運行,都不會出現問題。

1、JNIEnv指針只在它所在的線程中有效,不能跨線程傳遞和使用。不同線程調用一個本地方法時,傳入的JNIEnv指針是不同的。

2、局部引用只在創建它們的線程中有效,同樣不能跨線程傳遞。但可以把局部引用轉化成全局引用來供多線程使用。

 

8.1.2 監視器的入口和出口

 

監視器是JAVA平臺的基本同步機制。每一個對象都可以和一個監視器綁定:

synchronized (obj) {

     ...                   // synchronized block

 }

本地代碼中可以通過調用JNI函數來達到與上述JAVA代碼中等效的同步目的。這要用到兩個JNI函數:MonitorEnter負責進入同步塊,MonitorExit用來函數同步塊。

if ((*env)->MonitorEnter(env,obj) != JNI_OK) {

     ... /* error handling */

 }

 ...     /* synchronized block */

 if((*env)->MonitorExit(env, obj) != JNI_OK) {

     ... /* error handling */

 };

運行上面這段代碼時,線程必須先進入obj的監視器,再執行同步塊中的代碼。MonitorEnter需要傳入jobject作爲參數。同時,如果另一個線程已經進入了這個與jobject監視器的話,當前線程會阻塞。如果當前線程在不擁有監視器的情況下調用MonitorExit的話,會產生一個錯誤,並拋出一個IllegalMonitorStateException異常。上面的代碼中包含了MonitorEnter和MonitorExit這對函數的調用,在這對函數的使用時,我們一定要注意錯誤檢查,因爲這對函數有可能執行失敗(比如,建立監視器的資源分配不成功等原因)。這對函數可以工作在jclass、jstring、jarray等類型上面,這些類型的共同特徵是,都是jobject引用的特殊類型

有一個MonitorEnter方法,一定也要有一個與之對應的MonitorExit方法。尤其是在有錯誤或者異常需要處理的地方,要尤其小心。

if ((*env)->MonitorEnter(env,obj) != JNI_OK) ...;

 ...

 if((*env)->ExceptionOccurred(env)) {

     ... /* exception handling */

     /* remember to call MonitorExit here */

     if ((*env)->MonitorExit(env, obj) !=JNI_OK) ...;

 }

 ... /* Normalexecution path.

 if((*env)->MonitorExit(env, obj) != JNI_OK) ...;

調用MonitorEnter而不調用MonitorExit的話,很可能會引起死鎖。通過上面這段代碼和本節開始時的JAVA代碼的比較,你一定能發現用JAVA來進行同步要方便的多,所以,儘量用JAVA來做同步吧,把與同步相關的代碼都挪到JAVA中去吧。

 

8.1.3 監視器等待和喚醒

 

JAVA還提供了其它一些和線程監視器有關的API:Object.wait、Object.notify、Object.notifyAll。因爲監視器等待和喚醒操作沒有進入和退出操作對時效性要求那麼高,所以,沒有提供與這些方法相對應的JNI函數。我們可以通過JNI調用JAVA的機制來調用這些方法。

/* precomputed method IDs */

 static jmethodIDMID_Object_wait;

 static jmethodIDMID_Object_notify;

 static jmethodIDMID_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);

 }

上例中,我們假設Object.wait、Object.notify和Object.notifyAll已經在其它地方計算好並緩存在全局引用裏面了。

 

8.1.4 在任意地方獲取JNIEnv指針

 

前面我們提到了,JNIEnv指針只在當前線程中有效。那麼有沒有辦法可以從本地代碼的任意地方獲取到JNIEnv指針呢?比如,一個操作系統的回調函數中,本地代碼是無法通過傳參的方式獲取到JNIEnv指針的。

可以通過調用接口(invocation interface)中的AttachCurrentThread方法來獲取到當前線程中的JNIEnv指針:

JavaVM *jvm; /* already set */

 

 f()

 {

    JNIEnv *env;

     (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);

     ... /* use env */

 }

一旦當前線程被附加到JVM上,AttachCurrentThread函數就會返回一個屬於當前線程的JNIEnv指針。

有許多方式可以獲取JavaVM指針。可以在VM創建的時候記錄下來,也可以通過JNI_GetCreatedJavaVMs查詢被創建的虛擬機,還可以通過調用JNI函數GetJavaVM或者定義JNI_OnLoad句柄接口。與JNIEnv不同的是,JavaVM只要被緩存在全局引用中,是可以被跨線程使用的。

JDK1.2以後提供了一個新調用接口(invocation interface)函數GetEnv,這樣,你就可以檢查當前線程是否被附加到JVM上,然後返回屬於當前線程的JNIEnv指針。如果當前線程已經被附加到VM上的話,GetEnv和AttachCurrentThread在功能上是等價的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章