假定你又一个多线程应用程序,比如C实现的Web服务器。Http请求道来的时候,服务器创建了一定数目的Native线程用于并发的处理这些Http请求。我们想要在这个Wev服务器中嵌入一个Java虚拟机,这样多线程就可以同时在Java虚拟机中执行任务了。处理流程如下图所示:
![](https://box.kancloud.cn/2016-05-25_5745a067b07ee.png)
服务器衍生出的Native方法的生命周期可能比Java虚拟机中的线程要短。因此,因此我们需要将一个Native线程绑定到Java已经运行的虚拟机线程上,从这个Native线程中发起JNI调用,然后在不影响其他已绑定线程的前提下从虚拟机中解除绑定。
以下示例,attach.c,展示了如何使用Invocation interface将Native线程绑定到虚拟机。
>原书示例是用Win32 API写的,我这里的代码是在ubuntu上重写的,将thread相关的API换了而已。
~~~
#include <pthread.h>
#include <jni.h>
JavaVM * jvm;
#define PATH_SEPARATOR ';'
#define USER_CLASSPATH "."
void* thread_fun(void *arg){
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jobjectArray args;
jclass stringClass;
JNIEnv* env;
char buf[100];
int threadNum = (int)arg;
/* Pass NULL as the third argument */
#ifdef JNI_VERSION_1_2
res = (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
#else
res = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
#endif
if(res < 0) {
fprintf(stderr, "Attach failed\n");
return;
}
cls = (*env)->FindClass(env, "Prog");
if(!cls){
goto detach;
}
mid = (*env)->GetStaticMethodID(env,cls, "main", "([Ljava/lang/String;)V");
if(!mid){
goto detach;
}
sprintf(buf, " from Thread %d", threadNum);
jstr = (*env)->NewStringUTF(env, buf);
if(!jstr){
goto detach;
}
stringClass = (*env)->FindClass(env, "java/lang/String");
args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
if(!args){
goto detach;
}
(*env)->CallStaticVoidMethod(env,cls,mid,args);
detach:
if((*env)->ExceptionOccurred(env)) {
(*env)->ExceptionDescribe(env);
}
(*jvm)->DetachCurrentThread(jvm);
}
int main(){
JNIEnv *env;
int i;
jint res;
#ifdef JNI_VERSION_1_2
JavaVMInitArgs vm_args;
JavaVMOption options[1];
options[0].optionString = "-Djava.class.path=" USER_CLASSPATH;
vm_args.version = 0x00010002;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, (void**)&env, (void*)&vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s", vm_args.classpath, PATH_SEPERATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */
if(res <0){
fprintf(stderr, "Can't create java VM\n");
return 1;
}
for(i = 0; i < 5; ++i){
pthread_t thread;
pthread_create(&thread, NULL, thread_fun, (void*)i);
}
sleep(1);
(*jvm)->DestroyJavaVM(jvm);
}
~~~
以上的attach示例是在invoke.c上修改未来的,与invoke.c不同的是,它没有从主线程调用Prog.main,而是从Native代码启动的五个线程中调用的。在创建并启动子线程之后程序等待他们完成并调用DestroyJavaVM.每一个子线程会将自己绑定到Java虚拟机上并调用Prog.main函数,然后将自己从Java虚拟机上解除绑定。
JNI_AttachCurrentThread的第三个参数是NULL,Java 2 SDK 1.2加入了JNI_ThreadAttachArgs结构,允许你指定额外的参数,比如你想要绑定的线程组。
当程序执行函数DetachCurrentThread的时候,会释放所有该线程相关的所有局部引用。
执行程序将会产生以下输出:
~~~
Hello World from Thread 4
Hello World from Thread 1
Hello World from Thread 3
Hello World from Thread 2
Hello World from Thread 0
~~~
当然了,真实的输出次序可能根据实际情况有所不同。
- JNI编程指南翻译
- 第一部分:介绍与入门
- 介绍
- Java平台与宿主环境
- JNI的角色
- 使用JNI的潜在风险
- 什么时候应该使用JNI
- JNI的进化
- 示例程序
- 入门
- 概览
- 声明一个Native方法
- 编译HelloWorld类
- 创建Native方法头文件
- 实现Native方法
- 编译C源码并创建一个Native库
- 执行程序
- 第二部分:编程指南
- 基础类型, Strings, Arrays
- 一个简单的Native方法
- Native方法实现的C语言原型
- Native方法的参数
- 类型映射
- 访问Strings
- 转换成为Native Strings
- 释放Native Strings资源
- 创建新的Strings
- String相关的其他JNI函数
- Java SDK2 中的新的String相关的JNI方法
- JNI String函数总结
- 在String函数中选择
- 访问Arrays
- 在C语言中操作数组
- 访问基础类型的数组
- JNI基础类型数组操作函数总结
- 如何在基础类型数组函数中做选择
- 访问对象数组
- 属性和方法
- 访问属性
- 如何访问一个对象实例的属性
- 属性描述符
- 访问静态属性
- 调用方法
- 调用对象实例方法
- 获取方法描述符
- 调用静态方法
- 调用对象的基类方法
- 调用构造函数
- 缓存属性和方法ID
- 在使用时缓存
- 在类初始化的时候缓存
- 两种ID缓存方式的对比
- JNI属性和方法操作的性能
- 局部和全局引用
- 局部和全局引用说明
- 局部引用
- 全局引用
- 弱全局引用
- 引用的比较
- 引用资源的释放
- 释放局部引用
- JDK2中的局部引用管理
- 释放全局引用
- 管理引用的原则
- 异常
- 概览
- 在Native代码中捕获与抛出异常
- 工具函数
- 异常处理最佳实践
- 异常检查
- 异常处理
- 工具函数中的异常
- The Invocation Interface
- 创建Java虚拟机
- 链接ava虚拟机到Native应用程序
- 链接某个确定的虚拟机实现
- 链接未知虚拟机
- 绑定Native线程
- 一些JNI特性
- JNI与线程
- 约束
- 监视进入与退出
- 监视等待和通知
- 如何从当前Context中获取JNIEnv的指针
- 匹配线程模型
- 编写国际化代码
- 注册Native方法
- 加载与卸载时机
- 加载时机
- 卸载时机
- 反射的支持
- 使用C++实现JNI
- 改变现有的Native库
- 一对一映射关系
- 共享桩
- 一对一映射VS共享桩
- 共享桩的实现
- Peer类
- Java平台中的Peer类
- 释放Native数据结构
- Peer类背后的指针
- 缺陷与陷阱
- 错误检查
- 向JNI函数传递非法参数
- jclass与jobject的困惑
- jboolean类型参数的截断机制
- Java应用与Native代码之间的边界
- ID与引用的困惑
- 缓存属性和方法ID
- 终结Unicode 字符串
- Violating访问控制原则
- 无视国际化
- 保持虚拟机资源
- 被滥用的局部引用创建
- 跨线程使用JNIEnv
- 错误使用的线程模型
- 第三部分:详细说明
- JNI设计概览
- 设计目标
- 加载Native库
- 类加载器
- 类加载器与Native库
- 加载Native库
- 类型安全限制
- 卸载Native库
- 链接Native方法
- 调用转换
- JNIEnv接口指针
- JNIEnv接口指针的组织
- 接口指针的好处
- 传递数据
- 全局和局部引用
- 局部引用的实现
- 弱全局引用
- 访问对象
- 访问原始数组
- 属性和方法
- 错误和异常
- 如果没有对编码错误的检查
- Java虚拟机异常
- 异步异常
- JNI类型
- 原始类型和引用类型
- 原始类型
- 引用类型
- jvalue类型
- 属性和方法ID
- 字符串格式
- UTF-8字符串
- 类描述符
- 属性描述符
- 方法描述符
- 常量
- JNI函数
- JNI函数总结
- 直接导出调用的接口函数
- Java虚拟机接口
- 在Native库中定义的函数
- JNIEnv接口
- JNI函数使用规范