jni調試3(線程調試env變數問題),jnienv
jni層調試線程死機原因
一,導致死機原因:
jni層中 線程函數中 只要添加調用env 的函數 ,,就會死機 二,解決方案第一我們應該理解:①(獨立性) JNIEnv 是一個與線程相關的變數,即線程A有一個 JNIEnv變數, 線程B也有一個JNIEnv變數,由於線程相關,所以A線程不能使用B線程的 JNIEnv 結構體變數。
那麼如何保證了每個線程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儲存起來。(轉錄)
②(公用性) 先瞭解
TLS(thread-local storage)
線程是執行的單元,同一個進程內的多個線程共用了進程的地址空間,線程一般有自己的棧,但是如果想要實現某個全域變數在不同的線程之間取不同的值,而且不受影響。一種辦法是採用線程的同步機制,如對這個變數的讀寫之處加臨界區或者互斥量,但是這是以犧牲效率為代價的,能不能不加鎖呢?線程局部儲存就是幹這個的。
解決以上兩個問題:1首先定義全域變數
namespace android{ static JavaVM* gJavaVM = NULL; //定義一個全域Java VM引用對象 static jobject gJavaObj = NULL; //定義一個全域Java object對象,對於java層的類對象
......
2其次在調用某個線程的函數中定義:(保證線程在進程中資源的公用性,這兩句是把參數傳給所開線程)
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); }
3線上程函數中 引用: (保證線程函數 對應 env 和class)
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++); } }
三,我們在網上看到有些關於jni的程式資料用了(*env)->;
我們需要謹記:在linux下如果.c檔案中用 “env->” 編譯會找不到此結構,必須用“(*env)->”,或者改成.cpp檔案,以 c++的方式來編譯。
------------------------------------------------------------------------