jni基本操作 5.多线程临界区(类似 java 的同步代码块)处理


系列文章

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秒左右就结束了。


遇到的问题与注意点

  1. 向线程函数传递两个以上的参数,需要使用结构体,且传递的是结构体指针,该指针需要动态开辟内存空间。非动态开辟时,指针变量的存储空间会不足,而崩溃。
  2. test()的 vm 参数,是重写 JNI_OnLoad(JavaVM* vm, void* reserved) 时获得的
  3. 为什么需要向线程函数内传递 JavaVM*参数:
    由于 JniEnv* 无法线程共享,需要在子线程中单独获取,它的获取又是通过 JavaVM*的。
    vm->GetEnv(),若正确获取到,那表示线程已经附属过了;
    否则,再通过 vm->AttachCurrentThread(&env, NULL) 附属线程,拿到 env
  4. 为什么使用全局引用变量 g_ObjCall:
    首先,MonitorEnter(jobject)MonitorExit(jobject)这两函数操作的应该是同一个jobject型的变量。
    若在线程函数内创建 jobject,那首先想到的是类似这样 static jobject jobject1 = env->NewIntArray(1); 建一个 static 的引用变量;但是运行会发现崩溃了,static 创建引用变量是不行的。
    而全局引用变量,是可以满足要求的。
  5. 尝试过,在线程函数内,通过 env->FindClass("classpath"),再通过 env->AllocObject(jclass)来创建jobject型变量。
    运行发现在env->FindClass()时就崩溃了。
    因为子线程中的env,无法直接获取到自定义类,jdk 中的类是可以获取的。即使是通过全局引用的 jclass来创建jobject,也是会崩溃。
  6. 线程退出:
    在满足自定义的条件时,可以通过pthread_exit()退出线程,在退出前注意要先退出监视器,即调用 env->MonitorExit()
  7. 有想过,java 中创建的线程, MonitorEnter(jobject)MonitorExit(jobject)也能用于同步互斥?未尝试。

发布了400 篇原创文章 · 获赞 364 · 访问量 162万+

猜你喜欢

转载自blog.csdn.net/jjwwmlp456/article/details/89389725
今日推荐