JNI多個線程中使用

上一篇文章/kf/201202/119073.html說到 JNIEnv 是一個與線程相關的變量,即線程A有一個 JNIEnv變量, 線程B也有一個JNIEnv變量,由於線程相關,所以A線程不能使用B線程的 JNIEnv 結構體變量。

 

問題描述:

一個java對象通過JNI調用DLL中一個send()函數向服務器發送消息,不等服務器消息到來就立即返回,同時把JNI接口的指針JNIEnv *env(虛擬機環境指針),和jobject obj保存在DLL中的變量裡.一段時間後,DLL中的消息接收線程接收到服務器發來的消息,並試圖通過保存過的env和obj來調用先前的java對象的方法(相當於JAVA回調方法)來處理此消息此時程序會突然退出(崩潰).

即前臺JAVA線程發送消息,後臺線程處理消息,歸屬於兩個不同的線程,不能使用相同的JNIEnv變量,這裡可以利用一個機制: 利用全局的 JavaVM * 指針得到當前線程的 JNIEnv* 指針,與在C++中兩個線程使用TLS進行局部存儲類似的原理。

 

具體方法:

獲取全局的JavaVM變量:

/* Our VM */
JavaVM *g_vm;

env->GetJavaVM(&g_vm); //來獲取JavaVM指針.獲取瞭這個指針後,將該JavaVM保存起來。

 

線程 JNIEnv 指針,線程中獲取 JNIEnv 方法:

   JNIEnv *e;
   JavaVMAttachArgs thread_args;

 thread_args.name = "NFC Message Loop";
   thread_args.version = nat->env_version;
   thread_args.group = NULL;

   g_vm->AttachCurrentThread(&e, &thread_args); //後面的參數可以傳空

while(1){

//…

g_vm->DetachCurrentThread(); //使用完成後

 

經過如此以後,JNIEnv 就可以由每個線程獨自使用瞭。

 

而如果我們需要回調JAVA方法,jobject 也不能在多個線程中共享,如此可以在多個線程中使用瞭:

gs_object=env->NewGlobalRef(obj);//創建一個全局變量

將傳入的obj(局部變量)保存到gs_object中,從而其他線程可以使用這個gs_object(全局變量)來操縱這個java對象瞭

完整示例代碼如下:

java代碼:Test.java:

[java]
import java.io.*;   
class Test implements Runnable   
{   
 public int value  = 0;   
 static{ System.loadLibrary("Test");}   
   
 public native void setEnev();//本地方法     
   
public static void main(String args[]) throws Exception   
 {   
   Test t = new Test();   
<span style="color:#FF0000;">   t.setEnev(); //調用本地方法   </span>  
   
    while(true)   
    {    
      Thread.sleep(1000);   
      System.out.println(t.value);   
    }   
  }   

import java.io.*; 
class Test implements Runnable 

 public int value  = 0; 
 static{ System.loadLibrary("Test");} 
 
 public native void setEnev();//本地方法  
 
public static void main(String args[]) throws Exception 
 { 
   Test t = new Test(); 
<span style="color:#FF0000;">   t.setEnev(); //調用本地方法   </span>
 
    while(true) 
    {  
      Thread.sleep(1000); 
      System.out.println(t.value); 
    } 
  } 
}

 

JNI代碼 Test.cpp:
static JavaVM *gs_jvm=NULL; 
static jobject gs_object=NULL; 
static int gs_i=10; 
 
JNIEXPORT void JNICALL Java_Test_setEnev(JNIEnv *env, jobject obj) 

    env->GetJavaVM(&gs_jvm); //保存到全局變量中JVM  
    //直接賦值obj到DLL中的全局變量是不行的,應該調用以下函數:  
    gs_object=env->NewGlobalRef(obj); 
 
 HANDLE ht=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFun,0,NULL,NULL); 
}

void WINAPI ThreadFun(PVOID argv)//JNI中線程回調這個方法  
{  
 JNIEnv *env; 
 gs_jvm->AttachCurrentThread((void **)&env, NULL); 
 jclass cls = env->GetObjectClass(gs_object);   //獲取JAVA線程中的全局對象
 jfieldID fieldPtr = env->GetFieldID(cls,"value","I");   // 獲取JAVA對象
 
 while(1) 
 { 
    Sleep(100); 
   //這裡改變JAVA對象的屬性值(回調JAVA)  
   env->SetIntField(gs_object,fieldPtr,(jint)gs_i++); 
  } 
}

 

對於如上的思路,隻要你理解瞭TLS的用法就很容易理解以上內容瞭。

附加介紹 TLS (thread-local storage) 一下,網上摘抄的內容:

線程是執行的單元,同一個進程內的多個線程共享瞭進程的地址空間,線程一般有自己的棧,但是如果想要實現某個全局變量在不同的線程之間取不同的值,而且不受影響。一種辦法是采用線程的同步機制,如對這個變量的讀寫之處加臨界區或者互斥量,但是這是以犧牲效率為代價的,能不能不加鎖呢?線程局部存儲就是幹這個的。

 

Windows中是根據線程局部存儲索引來標識的(這個標識的分配和釋放由TlsAlloc和TlsFree完成),有瞭個這個”標識“就可以在各個線程中調用TlsGetValue或者TlsSetValue讀取或者設置各線程各自的值;

DWORD TlsAlloc(void); 

BOOL TlsFree(DWORD dwTlsIndex);
LPVOID TlsGetValue(DWORD dwTlsIndex);
BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue);

 

linux 平臺對應的接口函數:

int pthread_key_create(pthread_key_t * key, void (*)(void *));
int pthread_key_delete(pthread_key_t);
void *pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t, const void *);

摘自 andyhuabing的專欄

發佈留言