系列文章
jni基本操作 1. java 层创建 native 方法,并生成对应 jni 函数
jni基本操作 2. 操作java中的属性
jni基本操作 3. 操作java中的方法
jni基本操作 4. 加载与卸载函数,动态注册与反注册本地方法
jni基本操作 5.多线程临界区(类似 java 的同步代码块)处理
前言
在 本地代码中,要实现类似 java 中的 synchronized
功能。即临界区,同一时间只有一个线程能操作。
synchronized(obj) {
//这里就是临界区
}
功能实现:
使用 <pthread.h>
中的 pthread_create()
创建线程;
使用 『信号量 semaphore.h』的 sem_wait()和sem_post()
进行信号的等待与发送;或使用『 pthread.h』中互斥锁 pthread_mutex_lock()和pthread_mutex_unlock()
来实现。
jni 中也提供了类似功能的函数,那就是 MonitorEnter(jobject)
和MonitorExit(jobject)
。
关于 jobject:
通过 jni.h,可以知道,jclass、jstring、jarray、jthrowable 等都是 jobject 类型重命名的。
通常在多线程操作共享变量时,需要使用这种同步互斥的操作。
实例
写在 ThreadTest.cpp 文件中的。 (主要是 env->
写着方便啊 ^ _ ^)
#include <jni.h>
#include <pthread.h> //线程
#include <malloc.h>
#include <unistd.h>
#include <cstdlib> //c stdlib.h
#include <ctime> //c time.h
#include <cstdio> //c stdio.h
#include <android/log.h>
#ifndef LOG_TAG
#define LOG_TAG "stone.stone"
#define slogd(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#endif
struct Args3 {
JavaVM* vm;
int tid;
};
void* threadFun3(void* args); //线程函数;声明在前,方便test()使用。
void test(JavaVM *vm) {
pthread_t pid;
for(int i = 0; i < 10; i++) {
struct Args3 *args = (Args3 *)(malloc(sizeof(struct Args3)));
args->vm = vm;
args->tid = i;
//像如下这样传递,运行还是会报错的,是因为,指针变量的空间太小
// struct Args3 args3 = {vm, i};
// struct Args3* args = &args3;
pthread_create(&pid, nullptr, threadFun3, args);
}
}
static int count = 0;
void* threadFun3(void* arg) {
Args3* args = (struct Args3 *)arg;
JavaVM* vm = args->vm;
JNIEnv *env = nullptr;
int status = vm->GetEnv((void **)(&env), JNI_VERSION_1_6);
if (status < 0) {
slogd("stone->get env error. get 不到 env,就需要 attach");
status = vm->AttachCurrentThread(&env, nullptr);
if (status < 0) {
slogd("stone->AttachCurrentThread error");
return nullptr;
} else {
slogd("stone->AttachCurrentThread success");
}
}
env->MonitorEnter(g_ObjCall); //进入监视
int tid = args->tid;
time_t t = time(nullptr) + count;
srand(t);//设置随机因子,不同的随机因子,生成的随机数才不同
sleep(rand() % 3); //随机数 取模,再线程睡眠 0~2秒
count++;
slogd("stone->threadFun3. tid=%d, count=%d, time=%ld", tid, count, t);
env->MonitorExit(g_ObjCall); //退出监视
return nullptr;
}
在另一个主要的 cpp 文件中:
#include "ThreadTest.cpp"
extern jobject g_ObjCall;//可以被其它地方使用
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (JNI_OK != vm->GetEnv(reinterpret_cast<void**> (&env),JNI_VERSION_1_6)) {
slogd("JNI_OnLoad could not get JNI env");
return JNI_ERR;
}
//jclass clazz = env->FindClass("com/stone/ndk/jni/JniActivity");
//g_ObjCall = env->NewGlobalRef(env->AllocObject(clazz));
g_ObjCall = env->NewGlobalRef(env->NewIntArray(1));
test(vm);
return JNI_VERSION_1_6;
}
本例中,互斥操作有效,那么结果输出时,多数情况下,整体的运行时间是大于2秒的;若无效,那就是多线程并行运行了,整体运行时间2秒左右就结束了。
遇到的问题与注意点
- 向线程函数传递两个以上的参数,需要使用结构体,且传递的是结构体指针,该指针需要动态开辟内存空间。非动态开辟时,指针变量的存储空间会不足,而崩溃。
- test()的 vm 参数,是重写
JNI_OnLoad(JavaVM* vm, void* reserved)
时获得的 - 为什么需要向线程函数内传递
JavaVM*
参数:
由于JniEnv*
无法线程共享,需要在子线程中单独获取,它的获取又是通过JavaVM*
的。
先vm->GetEnv()
,若正确获取到,那表示线程已经附属过了;
否则,再通过vm->AttachCurrentThread(&env, NULL)
附属线程,拿到 env - 为什么使用全局引用变量 g_ObjCall:
首先,MonitorEnter(jobject)
和MonitorExit(jobject)
这两函数操作的应该是同一个jobject
型的变量。
若在线程函数内创建 jobject,那首先想到的是类似这样static jobject jobject1 = env->NewIntArray(1);
建一个 static 的引用变量;但是运行会发现崩溃了,static 创建引用变量是不行的。
而全局引用变量,是可以满足要求的。 - 尝试过,在线程函数内,通过
env->FindClass("classpath")
,再通过env->AllocObject(jclass)
来创建jobject
型变量。
运行发现在env->FindClass()
时就崩溃了。
因为子线程中的env,无法直接获取到自定义类,jdk 中的类是可以获取的。即使是通过全局引用的jclass
来创建jobject
,也是会崩溃。 - 线程退出:
在满足自定义的条件时,可以通过pthread_exit()
退出线程,在退出前注意要先退出监视器,即调用env->MonitorExit()
- 有想过,java 中创建的线程,
MonitorEnter(jobject)
和MonitorExit(jobject)
也能用于同步互斥?未尝试。