Chinaunix首页 | 论坛 | 博客
  • 博客访问: 447456
  • 博文数量: 145
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1060
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-22 11:52
个人简介

专注计算机技术: Linux Android 云计算 虚拟化 网络

文章分类

全部博文(145)

文章存档

2016年(3)

2015年(21)

2014年(75)

2013年(46)

我的朋友

分类: Android平台

2014-11-05 10:33:38

上一篇文章说到 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:

 

 

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();   
   t.setEnev(); //调用本地方法    
   
    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 *)

阅读(638) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

COMPUTER-TECH2015-03-19 21:04:32

1. java调用jni时,对应的jni函数有ENV的指针,直接引用就行了。如需回调到java层则也需要使用该env指针,否则奔溃。

2. java每次执行jni函数都是在不同的线程中,每次的env都不一样,注意了!

3. 另外一个就是java传下来的数组 在使用完后必须release否则,长时间运行,也会奔溃。