JNI开发-线程操作
JNI开发-线程操作
线程操作
JNIEnv指针仅在创建它的线程有效。C/C++创建的线程默认是没有附加到JVM的,如果我们需要在本地线程线程访问JVM,那么必须先调用AttachCurrentThread
将当前线程与JVM进行关联,然后才能获得JNIEnv对象。线程退出或不再需要使用JNIEnv时,我们必须通过调用DetachCurrentThread
来解除连接,否则可能会导致线程不能正常退出或程序奔溃等问题。
函数 | 说明 |
---|---|
AttachCurrentThread | 将当前线程附件到JVM |
DetachCurrentThread | 解除当前线程与JVM的连接 |
Java中访问native方法并没有线程限制,所以我们的本地代码并不一定只会运行在main线程中 ,同时本地C/C++也可用创建子线程,在多线程的情况下,就不得不考虑 线程同步问题了。 Java中,JDK为我们提供了synchronized来处理多线程同步代码块 ,相应的在JNI中也提供了两个函数来完成线程同步。
函数 | 说明 |
---|---|
MonitorEnter | 进入临界区 |
MonitorExit | 退出临界区 |
我们可以在 Native 代码中使用 POSIX 线程,就相当于使用一个库一样,首先需要包含这个库的头文件:
#include <pthread.h>
这个头文件中定义了很多和线程相关的函数,这里就暂时使用到了其中部分内容。
创建线程
POSIX 创建线程的函数如下:
int pthread_create(
pthread_t* __pthread_ptr,
pthread_attr_t const* __attr,
void* (*__start_routine)(void*),
void* arg);
它的参数对应如下:
- __pthread_ptr 为指向 pthread_t 类型变量的指针,用它代表返回线程的句柄。
- __attr 为指向 pthread_attr_t 结构的指针,可以通过该结构来指定新线程的一些属性,比如栈大小、调度优先级等,具体看 pthread_attr_t 结构的内容。如果没有特殊要求,可使用默认值,把该变量取值为 NULL 。
- 第三个参数为该线程启动程序的函数指针,也就是线程启动时要执行的那个方法,类似于 Java Runnable 中的 run 方法,它的函数签名格式如下:
void* start_routine(void* args)
启动程序将线程参数看成 void 指针,返回 void 指针类型结果。
- 第四个参数为线程启动程序的参数,也就是函数的参数,如果不需要传递参数,它可以为 NULL 。
pthread_create
函数如果执行成功了则返回 0 ,如果返回其他错误代码。
接下来,我们可以体验一下 pthread_create 方法创建线程。
void *printThreadHello(void *) {
cout<<("hello thread");
// 切记要有返回值
return NULL;
}
JNIEXPORT void JNICALL Java_com_jni_thread_TestDemo_naitveThread
(JNIEnv *, jobject) {
pthread_t pid;
pthread_create(&pid, nullptr, printThreadHello, nullptr);
pthread_join(pid, nullptr);
}
public class TestDemo {
static {
System.load("/Volumes/CodeApp/SourceWork/CPlus_workspace/JNI-Demo-Lib/cmake-build-debug/thread/libnative-lib.dylib");
}
public native void naitveThread(); // Java层 调用 Native层 的函数,完成JNI线程
public native void closeThread(); // 释放全局引用
public native void nativeFun1();
public native void nativeFun2(); // 2
public static native void staticFun3(); // 3
public static native void staticFun4();
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
testDemo.naitveThread();
}
}
RUN>
*******************👁输出结果👁************************* hello thread Process finished with exit code 0
将线程附着在 Java 虚拟机上
在上面的线程启动函数中,只是简单的执行了打印 log 的操作,如果想要执行和 Java 相关的操作,比如从 JNI 调用 Java 的函数等等,那就需要用到 Java 虚拟机环境了,也就是用到 JNIEnv 指针,毕竟所有的调用函数都是以它开头的。
pthread_create 创建的线程是一个 C++ 中的线程,虚拟机并不能识别它们,为了和 Java 空间交互,需要先把 POSIX 线程附着到 Java 虚拟机上,然后就可以获得当前线程的 JNIEnv 指针,因为 JNIEnv 指针只是在当前线程中有效。
通过 AttachCurrentThread
方法可以将当前线程附着到 Java 虚拟机上,并且可以获得 JNIEnv 指针。
AttachCurrentThread
方法是由 JavaVM 指针调用的,它代表的是 Java 虚拟机接口指针,可以在 JNI_OnLoad 加载时来获得,通过全局变量保存起来
static JavaVM *gVm = NULL;
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
gVm = vm;
return JNI_VERSION_1_6;
}
当通过 AttachCurrentThread
方法将线程附着当 Java 虚拟机上后,还需要将该线程从 Java 虚拟机上分离,通过 DetachCurrentThread
方法,这两个方法是要同时使用的,否则会带来 BUG 。
Native 线程中运行的方法:
class MyContext {
public:
JNIEnv *jniEnv = nullptr; // 不能跨线程 ,会奔溃
jobject instance = nullptr; // 不能跨线程 ,会奔溃
};
void *myThreadTaskAction(void *pVoid) { // 当前是异步线程
// 这两个是必须要的
// JNIEnv *env
// jobject thiz OK
MyContext *myContext = static_cast<MyContext *>(pVoid);
// TODO 解决方式 (安卓进程只有一个 JavaVM,是全局的,是可以跨越线程的)
JNIEnv *jniEnv = nullptr; // 全新的JNIEnv 异步线程里面操作
jint attachResult = ::gVm->AttachCurrentThread(reinterpret_cast<void **>(&jniEnv), nullptr); // 附加当前异步线程后,会得到一个全新的 env,此env相当于是子线程专用env
if (attachResult != JNI_OK) {
return 0; // 附加失败,返回了
}
// 1.拿到class
jclass mainActivityClass = jniEnv->GetObjectClass(myContext->instance);
// 2.拿到方法
jmethodID updateActivityUI = jniEnv->GetMethodID(mainActivityClass, "updateActivityUI", "()V");
// 3.调用
jniEnv->CallVoidMethod(myContext->instance, updateActivityUI);
::gVm->DetachCurrentThread(); // 必须解除附加,否则报错
cout << ("C++ 异步线程OK") << endl;
return nullptr;
}
JNIEXPORT void JNICALL Java_com_jni_thread_TestDemo_naitveThread
(JNIEnv *env, jobject job) {
MyContext *myContext = new MyContext;
myContext->jniEnv = env;
// myContext->instance = job; // 默认是局部引用,会奔溃
myContext->instance = env->NewGlobalRef(job); // 提升全局引用
pthread_t pid;
pthread_create(&pid, nullptr, myThreadTaskAction, myContext);
pthread_join(pid, nullptr);
}
public class TestDemo {
static {
System.load("/Volumes/CodeApp/SourceWork/CPlus_workspace/JNI-Demo-Lib/cmake-build-debug/thread/libnative-lib.dylib");
}
public native void naitveThread(); // Java层 调用 Native层 的函数,完成JNI线程
public native void closeThread(); // 释放全局引用
public native void nativeFun1();
public native void nativeFun2(); // 2
public static native void staticFun3(); // 3
public static native void staticFun4();
public void updateActivityUI() {
System.out.println("print thread name current thread name is " + Thread.currentThread().getName());
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
System.out.println("-----------------------");
testDemo.naitveThread();
}
}
RUN>
*******************👁输出结果👁************************* JNI_OnLoad ----------------------- print thread name current thread name is Thread-0 C++ 异步线程OK
下面来写测试不同线程之间JavaVM
和JNIEnv
是否相同
JNIEXPORT void JNICALL Java_com_jni_thread_TestDemo_nativeFun1
(JNIEnv *env, jobject job)
{
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址, JNI_OnLoad的jvm地址
printf("nativeFun1 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::gVm);
cout<< endl;
}
JNIEXPORT void JNICALL Java_com_jni_thread_TestDemo_nativeFun2
(JNIEnv *env, jobject job)
{
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址, JNI_OnLoad的jvm地址
printf("nativeFun2 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::gVm);
cout<< endl;
}
void * run(void *) { // native的子线程 env地址 和 Java的子线程env地址,一样吗 不一样的
JNIEnv * newEnv = nullptr;
::gVm->AttachCurrentThread(reinterpret_cast<void **>(&newEnv), nullptr);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址, JNI_OnLoad的jvm地址
printf("run jvm地址:%p, 当前run函数的newEnv地址:%p \n", ::gVm, newEnv);
cout<< endl;
::gVm->DetachCurrentThread();
return nullptr;
}
JNIEXPORT void JNICALL Java_com_jni_thread_TestDemo_staticFun3
(JNIEnv *env, jclass clazz)
{
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址, JNI_OnLoad的jvm地址
printf("nativeFun3 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::gVm);
cout<< endl;
// 调用run
pthread_t pid;
pthread_create(&pid, nullptr, run, nullptr);
}
JNIEXPORT void JNICALL Java_com_jni_thread_TestDemo_staticFun4
(JNIEnv *env, jclass clazz)
{
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址, JNI_OnLoad的jvm地址
printf("nativeFun4 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::gVm);
cout<< endl;
}
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
System.out.println("-----------------------");
testDemo.nativeFun1(); // main线程调用的
testDemo.nativeFun2(); // main线程调用的
staticFun3(); // main线程调用的
// 第四个 new Thread 调用 ThreadClass == clasz 当前函数clazz地址
new Thread() {
@Override
public void run() {
super.run();
staticFun4(); // Java的子线程调用
}
}.start();
}
RUN>
*******************👁输出结果👁************************* JNI_OnLoad ----------------------- nativeFun1 当前函数env地址0x7ff08000f1f8, 当前函数jvm地址:0x101efdfb0, 当前函数job地址:0x70000f08c9d8, JNI_OnLoad的jvm地址:0x101efdfb0 nativeFun2 当前函数env地址0x7ff08000f1f8, 当前函数jvm地址:0x101efdfb0, 当前函数job地址:0x70000f08c9d8, JNI_OnLoad的jvm地址:0x101efdfb0 nativeFun3 当前函数env地址0x7ff08000f1f8, 当前函数jvm地址:0x101efdfb0, 当前函数clazz地址:0x70000f08c9d0, JNI_OnLoad的jvm地址:0x101efdfb0 run jvm地址:0x101efdfb0, 当前run函数的newEnv地址:0x7ff0800939f8 nativeFun4 当前函数env地址0x7ff07f88d1f8, 当前函数jvm地址:0x101efdfb0, 当前函数clazz地址:0x700010142aa8, JNI_OnLoad的jvm地址:0x101efdfb0
![](https://img.haomeiwen.com/i17987810/c105e38caef9fa72.png)
JNIEnv
类型是一个指向全部JNI方法的指针,JNIEnv
提供了大部分 JNI 函数。JNIEnv
只在创建它的线程有效,<font color='red'>不能跨线程传递</font>,不能再线程之间共享 JNIEnv
。
![](https://img.haomeiwen.com/i17987810/5258fbb4d233b0ca.png)
![](https://img.haomeiwen.com/i17987810/86ffdf6e7d334a58.png)
![](https://img.haomeiwen.com/i17987810/0e7695c0d357410c.png)