JNI与多个线程导致JNIEnv*发生异常

1.概述:

JNIEnv 是一个与线程相关的变量,即线程A有一个 JNIEnv变量, 线程B也有一个JNIEnv变量,由于线程相关,所以A线程不能使用B线程的 JNIEnv 结构体变量。

2.问题描述:

一个java对象通过JNI调用DLL中一个send()函数向服务器发送消息,不等服务器消息到来就立即返回,同时把JNI接口的指针JNIEnv *env(虚拟机环境指针),和jobject obj保存在DLL中的变量里.一段时间后,DLL中的消息接收线程接收到服务器发来的消息,并试图通过保存过的env和obj来调用先前的java对象的方法(相当于JAVA回调方法)来处理此消息此时程序会突然退出(崩溃).

即前台JAVA线程发送消息,后台线程处理消息,归属于两个不同的线程,不能使用相同的JNIEnv变量,这里可以利用一个机制: 利用全局的 JavaVM * 指针得到当前线程的 JNIEnv* 指针,与在C++中两个线程使用TLS进行局部存储类似的原理。

3.具体方法:

获取全局的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++); 
  } 
}
4.总结: 

对于如上的思路,只要你理解了TLS的用法就很容易理解以上内容了。

附加介绍 TLS (thread-local storage) 一下,网上摘抄的内容:

线程是执行的单元,同一个进程内的多个线程共享了进程的地址空间,线程一般有自己的栈,但是如果想要实现某个全局变量在不同的线程之间取不同的值,而且不受影响。一种办法是采用线程的同步机制,如对这个变量的读写之处加临界区或者互斥量,但是这是以牺牲效率为代价的,能不能不加锁呢?线程局部存储就是干这个的。

Windows中是根据线程局部存储索引来标识的(这个标识的分配和释放由TlsAlloc和TlsFree完成),有了个这个”标识“就可以在各个线程中调用TlsGetValue或者TlsSetValue读取或者设置各线程各自的值;

DWORD TlsAlloc(void); 
<pre name="code" class="cpp" style="color: rgb(51, 51, 51); font-size: 14px; line-height: 28px; text-indent: 28px; ">DWORD TlsAlloc(void);
<span style="font-family: 宋体; text-indent: 2em; ">BOOL TlsFree(DWORD dwTlsIndex);</span>
LPVOID TlsGetValue(DWORD dwTlsIndex);BOOL TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue);

 

linux 平台对应的接口函数:

<div style="text-align: left;"><span style="font-family: 宋体; text-indent: 2em; ">int pthread_key_create(pthread_key_t * key, void (*)(void *));</span></div>int pthread_key_delete(pthread_key_t);
void *pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t, const void *);



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 Chap 4:用javah产生一个.h文件... 17 Chap5:jni教程(very very good) 19 Chap6: JNI传递返回值... 26 15.2.2.3 传递字符串... 28 15.2.2.4 传递整型数组... 29 15.2.2.5 传递字符串数组... 30 15.2.2.6 传递对象数组... 31 Chap7:Jni中C++和Java的参数传递... 33 Chap8:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组... 47 Chap5:使用JNI技术实现java程序调用第三方dll(c/c++)文件的功能... 47 Chap9:如何编写jni方法(转载)... 55 1、实例一:在jni中调用标准c中自带的函数printf(): 57 2、实例二、调用c 语言用户定义的函数... 58 3、实例三、在jni函数中访问java类中的对象实例域... 58 4、实例四:在jni函数中访问类的静态实例域... 60 5、实例五:在jni函数中调用java对象的方法... 60 6、实例六:在jni函数中调用java类的静态方法... 61 7、实例七:jni函数中传递基本数据类型参数... 62 8、实例八:在jni函数中传递对象类型参数... 62 9、实例九:在jni函数中处理字符串... 63 10、实例十:在jni函数中处理数组... 64 11、实例十一:在jni中的返回值问题... 65 12、实例十二:在jni中创建java类对象:... 66 Chap10:在 Windows 中实现 Java 本地方法... 66 1.Java 调用 C. 67 2.调试... 76 3.其他信息... 79 Chap11:如何在C/C++中调用Java. 80 1.环境搭建... 81 2.初始化虚拟机... 83 3.访问类方法... 85 4访问类属性... 87 5.访问构造函数... 88 6.数组处理... 89 7.中文处理... 89 8.异常... 91 9.线程和同步访问... 91 10.时间... 92 Chap12:基本JNI调用技术(c/c++与java互调) 93 Chap13:JNI的c代码中,另外一个线程获取 JNIEnv. 96 chap 14:当JNI遇到多线程--java对象如何被C++中的多个线程访问?. 97 chap 15:JNI多线程中的应用... 101 chap 16:JNI限制(多线程)... 105 chap 17:使用 Java Native Interface 的最佳实践... 106 1.性能缺陷... 107 2.正确性缺陷... 117 3.避免常见缺陷... 121 4.结束语... 128 Chap18:JNI设计实践之路... 129 一、 前言... 129 二、 JNI基础知识简介... 130 三、 Java程序调用非Java程序... 131 四、 C/C++访问Java成员变量和成员方法... 138 五、 异常处理... 140 六、 MFC程序中嵌入Java虚拟机... 142 Chap19:JNI编程系列之基础篇... 148 System.loadLibrary("HelloWorld"); 149 JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); 150 Chap20:JNI编程系列之中级篇(上)... 151 1. Java基本类型的传递... 151 2. String参数的传递... 151 3. 数组类型的传递... 153 4. 二维数组和String数组... 154 Chap21:JNI编程系列之高级篇... 155 1. 在一般的Java类中定义native方法... 156 2. 访问Java类的域和方法... 156 3. 在native方法中使用用户定义的类... 157 4. 异常处理... 158

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值