本文是《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. jquery.mobiscroll仿Iphone ActionSheet省市区联动

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="u ...

  2. codevs 1388 砍树

    时间限制: 1 s  空间限制: 256000 KB  题目等级 : 黄金 Gold 题目描述 Description 伐木工人米尔科需要砍倒M米长的木材.这是一个对米尔科来说很容易的工作,因为他有一 ...

  3. Mysql完全手册(笔记二,使用数据与性能优化)

    一.使用数据 1.使用变量 MySQL也可以让我们以用户自定义的变量来存储select查询的结果,以便在将来select查询中使用.它们只会在客户会话期间存在,但是它们提供一个方便有效的方法来连接查询 ...

  4. EF里的默认映射以及如何使用Data Annotations和Fluent API配置数据库的映射

    I.EF里的默认映射 上篇文章演示的通过定义实体类就可以自动生成数据库,并且EF自动设置了数据库的主键.外键以及表名和字段的类型等,这就是EF里的默认映射.具体分为: 数据库映射:Code First ...

  5. 高品质开源工具Chloe.ORM:支持存储过程与Oracle

    扯淡 这是一款高质量的.NET C#数据库访问框架(ORM).查询接口借鉴 Linq.借助 lambda 表达式,可以完全用面向对象的方式就能轻松执行多表连接查询.分组查询.聚合查询.插入数据.批量删 ...

  6. 「post中文参数问题」以及「验证码自动识别备忘」

    前言 之前搞过几次模拟登录,都是模拟 post 后取到 cookie,之后便能用这个 cookie 愉快玩耍.这次碰到了验证码,其实只需手动登录一次,手动取到 cookie 后也能玩耍,不过 cook ...

  7. C 语言学习 第六次作业总结

    本次作业,同学们开始学习函数.通过之前和同学们的沟通,似乎同学们在这里遇到的问题比较多.下面,我先帮同学们整理下函数的相关知识点. 什么是函数 首先,需要明确的是,什么是函数.所谓函数,也就是一段有名 ...

  8. 请写一个php函数,可以接受任意数量的参数

    请写一个php函数,可以接受任意数量的参数 这是一道面试题.怎么写这个函数呢? function fun(......) { } ----------------------------------- ...

  9. iOS10推送通知适配

    iOS10推送新增了UserNotifications Framework,使用起来其实很简单. 只是在iOS10以上系统上点击通知栏,回调方法不再走原来的这两个方法 - (void)applicat ...

  10. Java学习笔记(三)

    今天主要学习了ant ant概述 ant是一个将软件编译.测试.部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发.在实际软件开发中,有很多地方可以用到ant 开发环境: Sy ...