本文是《The Java Native Interface Programmer’s Guide and Specification》读书笔记

JNI里的多线程

在本地方法里写有关多线程的代码时,需要知道下面几个约束:

  1. 一个JNIEnv指针只在与它关联的线程里有效,也就是说,在线程间传递JNIEnv指针和在多线程环境里通过缓存来使用它是不允许和不安全的。JVM在同一个线程里多次调用同一个本地方法时传递的是同一个JNIEnv指针,但在不同的线程里调用同一个本地方法时传递的是不同的JNIEnv指针。
  2. 本地引用只在创建它的线程里有效,也就是说你不能在线程间传递本地引用。因为在多线程的环境里可能会使用到相同的引用,因此我们需要将本地引用转型为全局引用。

JNI里的同步机制(类似于锁的获取与释放)

在本地方法里,可以通过JNI函数来实现Java里的同步块(互斥资源的使用),用方法MonitorEnter来得到一个JNI引用的监控器(锁),方法MonitorExit释放监控器(锁的释放),下面是简单的使用场景:

//省略了其他代码,下面只是本地方法实现代码里的某一部分
if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
... /* 获取obj引用的锁失败,进行相应的处理 */
}
/* 编写需要同步的代码块 */
....
if ((*env)->ExceptionOccurred(env)) {
... /* 异常处理的代码 */
/* 在这里要记得调用 MonitorExit来释放所获得的监控器 */
if ((*env)->MonitorExit(env, obj) != JNI_OK) ...;
}
/*正常的调用MonitorExit来释放锁*/
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
... /* 释放obj引用的锁失败,进行相应的处理 */
};

需要注意的是,在同步代码块里可能会发生异常,我们需要的对应的异常处理代码中调用MonitorExit方法来释放锁,如果忘记调用这个释放监控器的方法,可能会导致死锁的发生。因为在JNI里使用同步机制会比较麻烦,因此我们尽可能在ava的程序里来实现相应的同步机制。

Java API中提供了一些对线程间同步非常有用的方法,如Object.wait,Object.notify,Object.notifyAll来等待获取一个对象的锁,唤醒等待获得对象锁的对象等。但在JNI里并没有提供对应的方法来等待获取对象的监控器,唤醒等待获取对象监控器的对象,因此在JNI里通常采用JNI的方法调用机制来调用对应的Java方法来实现相应的操作。在下面的代码里,我们假设已经获得了相应方法的methodID缓存在全局引用中;

/* precomputed method IDs */
static jmethodID MID_Object_wait;
static jmethodID MID_Object_notify;
static jmethodID MID_Object_notifyAll;
void
JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout)
{
(*env)->CallVoidMethod(env, object, MID_Object_wait,
timeout);
}
void
JNU_MonitorNotify(JNIEnv *env, jobject object)
{
(*env)->CallVoidMethod(env, object, MID_Object_notify);
}
void
JNU_MonitorNotifyAll(JNIEnv *env, jobject object)
{
(*env)->CallVoidMethod(env, object, MID_Object_notifyAll);
}

在任意上下文中获取JNIEnv指针

在首页的介绍中,我们知道JNIEnv指针只在与它相关联的线程里是有效的,一般对于本地方法来说,他们是从JVM中得到这个指针作为方法的第一个参数的。但也有一小部分本地代码不是从JVM中直接得到这个JNIEnv指针的,比如本地方法里的某一部分代码是属于操作系统调用的某一个方法的,因此这可能就导致将JNIEnv指针作为参数是没有用的。这时我们就可以调用方法AttachCurrentThread来得到当前线程的JNIEnv指针。只要当前这个线程已经加载到JVM中,就可以返回正确的指针。

JavaVM *jvm; /* already set */
f()
{
JNIEnv *env;
(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
... /* use env */
}

上面的代码里,需要先获取一个JVM的指针,JNI里有许多方法可以得到一个JVM指针,JNI_GetCreatedJavaVMs,GetJavaVM等。并且JVM指针是可以保存在全局引用里的。

本地代码的注册

本地代码的注册方式有两种:

  1. 在执行本地方法前,在Java代码里使用语句System.loadLibrary("foo")加载本地方法所有的链接库;
  2. 在本地方法的实现里,需要用到其他链接库里的本地方法时,第一种方法就不适用了,比如,在本地方法里声明另一个本地方法void JNICALL g_impl(JNIEnv *env, jobject self);但它的实现是在另一个链接库里实现的,则需要使用下面的代码来作为这个方法的实现:
//这里某一个本地方法里的代码,省略了其他部分代码
JNINativeMethod nm;
nm.name = "g";//需要使用的其他链接库里的本地方法的名字
/* 方法的描述,如返回值,参数等 */
nm.signature = "()V";
nm.fnPtr = g_impl;//在这里的本地方法的声明的名字
//注册g_impl方法
(*env)->RegisterNatives(env, cls, &nm, 1);

方法g_imlp的声明并不需要遵循JNI的命名规范,因为这只是调用时的方法指针,并不需要展开代码(调用这个方法时,实际调用的是另一个链接库里名为g的JNI方法),所有不需要使用JNIEXPORT,但需要遵循JNI的调用规范。

使用动态注册链接库的方法的好处为:

其他

将本地代码创建的string对象转型为jstring对象返回给Java程序:

jstring JNU_NewStringNative(JNIEnv *env, const char *str)
{
jstring result;
jbyteArray bytes = 0;
int len;
if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
return NULL; /* out of memory error */
}
len = strlen(str);
bytes = (*env)->NewByteArray(env, len);
if (bytes != NULL) {
(*env)->SetByteArrayRegion(env, bytes, 0, len,
(jbyte *)str);
result = (*env)->NewObject(env, Class_java_lang_String,
MID_String_init, bytes);
(*env)->DeleteLocalRef(env, bytes);
return result;
} /* else fall through */
return NULL;
}

将jstring对象转型为本地string对象

char *JNU_GetStringNativeChars(JNIEnv *env, jstring jstr)
{
jbyteArray bytes = 0;
jthrowable exc;
char *result = 0;
if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
return 0; /* out of memory error */
}
bytes = (*env)->CallObjectMethod(env, jstr,
MID_String_getBytes);
exc = (*env)->ExceptionOccurred(env);
if (!exc) {
jint len = (*env)->GetArrayLength(env, bytes);
result = (char *)malloc(len + 1);
if (result == 0) {
JNU_ThrowByName(env, "java/lang/OutOfMemoryError",
0);
(*env)->DeleteLocalRef(env, bytes);
return 0;
}
(*env)->GetByteArrayRegion(env, bytes, 0, len,
(jbyte *)result);
result[len] = 0; /* NULL-terminate */
} else {
(*env)->DeleteLocalRef(env, exc);
}
(*env)->DeleteLocalRef(env, bytes);
return result;

Java Native Interface 五 JNI里的多线程与JNI方法的注册的更多相关文章

  1. Java Native Interface 六JNI中的异常

    本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 在这里只讨论调用JNI方法可能会出现的异常, ...

  2. Java Native Interface 四--JNI中引用类型

    本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 JNI支持将类实例和数组类型(如jobjec ...

  3. Java Native Interface 二 JNI中对Java基本类型和引用类型的处理

    本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 Java编程里会使用到两种类型:基本类型(如 ...

  4. android 学习随笔二十七(JNI:Java Native Interface,JAVA原生接口 )

    JNI(Java Native Interface,JAVA原生接口) 使用JNI可以使Java代码和其他语言写的代码(如C/C++代码)进行交互. 问:为什么要进行交互? 首先,Java语言提供的类 ...

  5. JNI(Java Native Interface)

    一.JNI(Java Native Interface)        1.什么是JNI:               JNI(Java Native Interface):java本地开发接口   ...

  6. Java Native Interface Specification(JNI)

    Java Native Interface Specification(JNI) 使用场景: 需要的功能,标准的java不能提供 有了一个用其他的语言写好的工具包,希望用java去访问它 当需要高性能 ...

  7. 【详解】JNI (Java Native Interface) (四)

    案例四:回调实例方法与静态方法 描述:此案例将通过Java调用的C语言代码回调Java方法. 要想调用实例对象的方法,需要进行以下步骤: 1. 通过对象实例,获取到对象类的引用  => GetO ...

  8. 【详解】JNI (Java Native Interface) (三)

    案例三:C代码访问Java对象的实例变量   获取对象的实例变量的步骤: 1. 通过GetObjectClass()方法获得此对象的类引用 2. 通过类引用的GetFieldID()方法获得实例变量的 ...

  9. 【详解】JNI (Java Native Interface) (二)

    案例二:传递参数给C代码,并从其获取结果 注:这里传递的参数是基本类型的参数,在C代码中有直接的映射类型. 此案例所有生成的所有文件如下: (1)编写案例二的Java代码,如下: 这里我们定义了一个n ...

随机推荐

  1. Oracle 表和表数据恢复

    1. 表恢复 对误删的表,只要没有使用 purge 永久删除选项,那么基本上是能从 flashback table 区恢复回来的. 数据表和其中的数据都是可以恢复回来的,记得 flashback ta ...

  2. CentOS升级openssl

    才设置了http2,结果蓝狗说我网站不安全,检测一下发现openssl有漏洞,于是准备升级一下openssl 检测网站: www.ssllabs.com/ssltest/analyze.html # ...

  3. mvn-打jar运行包(含环境变量配置)

    前沿条件 maven下载:http://maven.apache.org/download.cgi 配置环境变量 MAVEN_HOME= D:\Softwares\apache-maven-3.2.2 ...

  4. 冰冻三尺非一日之寒--js dom

    1. 写页面是觉得丑        float,clear:both,margin,padding        position:            left:                网 ...

  5. Django进阶(二)

    Template 之前的好多HTML文件中都包含类似"{{ }}"."{% %}",其实他们都是模板语言,模板本质上是HTML,但是夹杂了一些变量和标签,可以方 ...

  6. 关系数据库—SQL学习笔记

    SQL的特点: 综合统一 高度非过程化(存取路径的选择以及SQL的操作过程由系统自动完成) 面向集合的操作方式,以同一种语法结构提供多种使用方法(可以在终端键盘上直接键入SQL命令对数据库进行操作,也 ...

  7. 添加webservice调用日志

    之前想用spring的AOP给webservice添加切面的,但是使用around切面后,居然调用端得不到webservice的返回结果,而且报文的详细情况也不得而知,很是尴尬,所以偷了个懒.但是该做 ...

  8. kafka集群安装部署

    kafka集群安装 使用的版本 系统:centos6.5 centos6.7 jdk:1.7.0_79 zookeeper:3.4.9 kafka:2.10-0.10.1.0 一.环境准备[只列,不具 ...

  9. Android中利用AIDL机制调用远程服务

    服务端: //CalculateInterface.aidl package com.itheima.aidl.calculate; interface CalculateInterface { do ...

  10. maven打包jar包

    参考http://www.cnblogs.com/justinzhang/p/4983633.html 新建jar类型的maven project (选择simple project ) 配置 < ...