A. 如何真正地殺死一個JNI中創建的線程
如果你想了解JNI在如何在多線程下使用
如果你在子線程使用JNI時遇到findClass不能找到目標Class,而在主線程下卻能找到該Class的問題。或是GetEnv返回NULL的問題
如果你想多學點編程技術的話
那麼,這篇文章就是為你而寫的, :)
最近工作中遇到這么個問題:c++代碼需要調用Android的API來做一個比較耗時的任務,因為有點耗時,希望能有個進度條顯示給用戶,很自然地,我創建了一個子線程用來執行這個耗時的任務,按照平時寫法,結果一運行,GetEnv獲取失敗了。網上查找一番,官方說明有這么句話:
If the current thread is not attached to the VM, sets *env to NULL, and returns JNI_EDETACHED. If the specified version is not supported, sets *env to NULL, and returns JNI_EVERSION. Otherwise, sets *env to the appropriate interface, and returns JNI_OK.
調試後找到了原因,the current thread is not attached。 原來子線程函數里需要使用AttachCurrentThread()和DetachCurrentThread()這兩個函數。沒錯,你需要gJvm->AttachCurrentThread(&env, NULL);來獲取env,這樣寫之後,以為萬事大吉了,結果findClass出錯了,沒有找到目標類,可是我千真萬確地記得在主線程里這么寫是沒有問題的。env沒有問題了,這回又哪裡出錯了呢?上網google一番,噢,不對,google被牆了,是用bing查找一番後,總算有些眉目了。
首先確保你的class name寫對了,以包名開頭,並用反斜杠隔開。如果class name沒有錯,那麼應該是class loader的問題了。解決方法是你先在主線程中獲取該class,並且將其保存為全局變數,以便其他線程使用。
jclass tmp = env->FindClass("com/example/company/MyClass");
myClass = (jclass)env->NewGlobalRef(tmp);
在子線程中,
mid = env->GetStaticMethodID(cls, "fromJNI", "(I)V");
if (mid != NULL)
{
env->CallStaticVoidMethod(env, cls, mid, i);
}
當然,也有其他解決方法,至少我使用這種方法成功了。而接下來在java中調用c++的代碼就比較順利了,木有碰到問題了。
總結:
1.在JNI_OnLoad中,保存JavaVM*,這是跨線程的,持久有效的,而JNIEnv*則是當前線程有效的。一旦啟動線程,用AttachCurrentThread方法獲得env。
2.通過JavaVM*和JNIEnv可以查找到jclass。
3.把jclass轉成全局引用,使其跨線程。
4.然後就可以正常地調用你想調用的方法了。
5.用完後,別忘了delete掉創建的全局引用和調用DetachCurrentThread方法。
B. Runable和thread的區別
在java中可有兩種方式實現多線程,一種是繼承Thread類,一種是實現Runnable介面; Thread類是在java.lang包中定義的。一個類只要繼承了Thread類同時覆寫了本類中的 run()方法就可以實現多線程操作了,但是一個類只能繼承一個父類,這是此方法的局限, 下面看例子: package org.thread.demo; class MyThread extends Thread{ private String name; public MyThread(String name) { super(); this.name = name; } public void run(){ for(int i=0;i<10;i++){ System.out.println("線程開始:"+this.name+",i="+i); } } } package org.thread.demo; public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("線程a"); MyThread mt2=new MyThread("線程b"); // thread1,thread2,按順序進行 mt1.run(); mt2.run(); } } 但是,此時結果很有規律,先第一個對象執行,然後第二個對象執行,並沒有相互運行。在JDK的文檔中可以發現,一旦調用start()方法,則會通過JVM找到run()方法。下面啟動 start()方法啟動線程: package org.thread.demo; public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("線程a"); MyThread mt2=new MyThread("線程b"); //亂序進行 mt1.start(); mt2.start(); } }; 這樣程序可以正常完成互動式運行。那麼為啥非要使用start()方法啟動多線程呢? 在JDK的安裝路徑下,src.zip是全部的java源程序,通過此代碼找到Thread中的start()方法的定義,可以發現此方法中使用了private native void start0();其中native關鍵字表示可以調用操作系統的底層函數,那麼這樣的技術成為JNI技術(java Native Interface) ·Runnable介面 在實際開發中一個多線程的操作很少使用Thread類,而是通過Runnable介面完成。 public interface Runnable{ public void run(); } 例子: package org.runnable.demo; class MyThread implements Runnable{ private String name; public MyThread(String name) { this.name = name; } public void run(){ for(int i=0;i<100;i++){ System.out.println("線程開始:"+this.name+",i="+i); } } }; 但是在使用Runnable定義的子類中沒有start()方法,只有Thread類中才有。此時觀察Thread類,有一個構造方法:public Thread(Runnable target) 此構造方法接受Runnable的子類實例,也就是說可以通過Thread類來啟動Runnable實現的多 線程。(start()可以協調系統的資源): package org.runnable.demo; import org.runnable.demo.MyThread; public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("線程a"); MyThread mt2=new MyThread("線程b"); new Thread(mt1).start(); new Thread(mt2).start(); } }