【JNI编程】JNI中使用Linux本地线程

我们知道Android系统内核是使用Linux实现的,那么在Android OS中JNI实现的本地代码中实际使用的是Linux线程,这就需要pthread支持。

一、涉及pthread方法

Linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。

头文件

#include<pthread.h>

1.1 pthread_create

pthread_create是UNIX环境创建线程函数

函数声明

int pthread_create(pthread_t*restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);

返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于制定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。

由 restrict 修饰的指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由 restrict 修饰的指针表达式中。 由 restrict 修饰的指针主要用于函数形参,或指向由 malloc() 分配的内存空间。restrict 数据类型不改变程序的语义。编译器能通过作出 restrict 修饰的指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。

返回值

若成功则返回0,否则返回出错编号。

参数

第一个参数为指向线程标识符的指针。

第二个参数用来设置线程属性。

第三个参数是线程运行函数的起始地址。

最后一个参数是运行函数的参数。

1.2 pthread_join

函数pthread_join用来等待一个线程的结束,线程间同步的操作。

函数声明

int pthread_join(pthread_t thread, void **retval);

pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

返回值

若成功则返回0,否则返回出错编号。

参数

thread: 线程标识符,即线程ID,标识唯一线程。

retval: 用户定义的指针,用来存储被等待线程的返回值。

1.3 pthread_self

pthread_self是一种函数,功能是获得线程自身的ID。

函数声明

pthread_t pthread_self(void);

获得线程自身的ID。pthread_t的类型为unsigned long int,所以在打印的时候要使用%lu方式,否则显示结果出问题。

返回值

pthread_t的类型为unsigned long int

1.4 pthread_exit

线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。

函数声明

void pthread_exit(void* retval);

这个函数的作用是终止调用它的线程,并返回一个指向某个对象的指针。

二、涉及JNI方法

2.1 AttachCurrentThread

jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);

将当前线程attach到Java VM。 返回JNIEnv参数中的JNI接口指针。

试图attach已经attach的线程是一个无操作。

本地线程无法同时连接到两个Java VM。

当线程attach到VM时,上下文类加载器是引导加载程序。

LINKAGE:

JavaVM接口函数表中的索引4。

PARAMETERS:

vm:当前线程将attach到的VM。

p_env:指向当前线程的JNI接口指针所在位置的指针。

thr_args:可以是NULL或指向JavaVMAttachArgs结构的指针,以指定其他信息:

从JDK/JRE 1.1开始,AttachCurrentThread的第二个参数总是一个指向JNIEnv的指针。AttachCurrentThread的第三个参数是保留的,应该设置为NULL。

从JDK/JRE 1.2开始,将NULL作为第三个参数传递和1.1保持一致,或者传递一个指向以下结构的指针来指定其他信息:

typedef struct JavaVMAttachArgs {
    jint version;  /* 必须至少为JNI_VERSION_1_2 */
    char *name;    /* 线程的名称使用修改后的UTF-8字符串,或NULL */
    jobject group; /* ThreadGroup对象的全局引用,或NULL */
} JavaVMAttachArgs

RETURNS:

成功时返回JNI_OK; 失败时返回合适的JNI错误代码(负数)。

2.2 DetachCurrentThread

jint DetachCurrentThread(JavaVM *vm);

从Java VM分离当前线程。该线程持有的所有Java监视器都将被释放。等待此线程死亡的所有Java线程都会收到通知。

从JDK/JRE 1.2开始,主线程可以从VM分离。

LINKAGE:

JavaVM接口函数表中的索引5。

PARAMETERS:

vm: 当前线程将从其中分离的vm。

RETURNS:

成功时返回JNI_OK; 失败时返回合适的JNI错误代码(负数)。

三、使用本地线程

JNI接口指针(JNIEnv)仅在当前线程中有效。 如果另一个线程需要访问Java VM,它必须首先调用AttachCurrentThread()以将自身连接到VM并获取JNI接口指针。一旦连接到VM,本地线程就像在本地方法中运行的普通Java线程一样工作。本地线程保持连接到VM,直到它调用DetachCurrentThread()来分离自身。

连接的线程应该有足够的堆栈空间来执行合理的工作量。 每个线程的堆栈空间分配是特定于操作系统的。例如,使用pthreads,可以在pthread_create的pthread_attr_t参数中指定堆栈大小。

连接到VM的本地线程必须在退出之前调用DetachCurrentThread()以分离自身。如果调用堆栈上有Java方法,则线程无法分离。

以下代码片段为Java代码,运行于Android OS:

package ndk.example.com.ndkexample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText("启动10条Native线程...");
        JniCall jniCall = new JniCall();
        mainThread(jniCall, 10);
    }

    public native void mainThread(JniCall jniCall, int num);
}

package ndk.example.com.ndkexample;

import android.util.Log;

final public class JniCall {

    public void fromNativeCall(int i, int j) {
        Log.d("Native", "fromNativeCall i=" + i + ",j=" + j);
    }

}

本地代码如下:

#include <jni.h>
#include <stdlib.h>
#include <pthread.h>
#include <android/log.h>

#define TAG "Native"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))

JavaVM *g_jvm = NULL;
jobject g_obj = NULL;

struct two_parameter {
    int i;
    int j;
};

void *thread_fun(void *arg) {
    JNIEnv *localEnv;
    jclass cls;
    jmethodID mid;

    if (g_jvm->AttachCurrentThread(&localEnv, NULL) != JNI_OK) {
        LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
        return NULL;
    }

    cls = localEnv->GetObjectClass(g_obj);
    if (cls == NULL) {
        LOGE("GetObjectClass() Error.....");
        goto error;
    }

    mid = localEnv->GetMethodID(cls, "fromNativeCall", "(II)V");
    if (mid == NULL) {
        LOGE("GetMethodID() Error.....");
        goto error;
    }

    two_parameter *two;
    two = (two_parameter *) arg;
    LOGI("tid=%lu i=%d j=%d", (unsigned long int) pthread_self(), two->i, two->j);
    localEnv->CallVoidMethod(g_obj, mid, two->i, two->j);

    error:
    if (g_jvm->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread() failed", __FUNCTION__);
    }

    pthread_exit(0);
}

extern "C" JNIEXPORT void
JNICALL
Java_ndk_example_com_ndkexample_MainActivity_mainThread(JNIEnv *env, jobject /* this */, jobject jniCallObj, jint threadNum) {

    env->GetJavaVM(&g_jvm);
    g_obj = env->NewGlobalRef(jniCallObj);

    int i;
    pthread_t *pt;
    pt = (pthread_t *) malloc(threadNum * sizeof(pthread_t));

    for (i = 0; i < threadNum; i++) {
        two_parameter *two = (two_parameter *) malloc(sizeof(two_parameter));
        two->i = i;
        two->j = (i + 1);
        LOGI("main thread two_parameter i=%d j=%d", two->i, two->j);
        pthread_create(&pt[i], NULL, &thread_fun, two);
    }

    for (i = 0; i < threadNum; i++) {
        pthread_join(pt[i], NULL);
    }

    env->DeleteGlobalRef(g_obj);
    LOGI("main thread exit.....");
}

我们在本地方法mainThread中创建了10条本地线程,每条本地线程都执行本地方法thread_fun,并在其中调用了AttachCurrentThread获取JNIEnv,拿到JNI环境上下文后,就可以获得Java类的jclass和jmethodID,从而可以调用Java中JniCall对象的方法fromNativeCall(int i, int j)了,最后调用了DetachCurrentThread解除了在VM中的Attach状态,将线程从VM中分离。

当然在创建线程时给执行函数传入了多个参数,所以最后传入了一个自定义的结构体指针。并且在所有线程创建以后,执行pthread_join进行等待。还将mainThread中传入的jniCallObj提升为全局引用,并使用env->GetJavaVM()拿到JavaVM的全局引用。

最后来看运行结果:

03-03 19:59:19.230 8384-8384/ndk.example.com.ndkexample I/Native: main thread two_parameter i=0 j=1
    main thread two_parameter i=1 j=2
    main thread two_parameter i=2 j=3
    main thread two_parameter i=3 j=4
    main thread two_parameter i=4 j=5
    main thread two_parameter i=5 j=6
    main thread two_parameter i=6 j=7
    main thread two_parameter i=7 j=8
    main thread two_parameter i=8 j=9
    main thread two_parameter i=9 j=10
03-03 19:59:19.230 8384-8403/ndk.example.com.ndkexample I/Native: tid=1073842064 i=6 j=7
03-03 19:59:19.230 8384-8404/ndk.example.com.ndkexample I/Native: tid=1074648600 i=7 j=8
03-03 19:59:19.230 8384-8402/ndk.example.com.ndkexample I/Native: tid=1073841480 i=5 j=6
03-03 19:59:19.230 8384-8401/ndk.example.com.ndkexample I/Native: tid=1073840896 i=4 j=5
03-03 19:59:19.240 8384-8405/ndk.example.com.ndkexample I/Native: tid=1074649184 i=8 j=9
03-03 19:59:19.240 8384-8402/ndk.example.com.ndkexample D/Native: fromNativeCall i=5,j=6
03-03 19:59:19.240 8384-8401/ndk.example.com.ndkexample D/Native: fromNativeCall i=4,j=5
03-03 19:59:19.240 8384-8404/ndk.example.com.ndkexample D/Native: fromNativeCall i=7,j=8
03-03 19:59:19.240 8384-8403/ndk.example.com.ndkexample D/Native: fromNativeCall i=6,j=7
03-03 19:59:19.240 8384-8400/ndk.example.com.ndkexample I/Native: tid=1073840312 i=3 j=4
03-03 19:59:19.240 8384-8405/ndk.example.com.ndkexample D/Native: fromNativeCall i=8,j=9
03-03 19:59:19.240 8384-8400/ndk.example.com.ndkexample D/Native: fromNativeCall i=3,j=4
03-03 19:59:19.240 8384-8399/ndk.example.com.ndkexample I/Native: tid=1073839728 i=2 j=3
03-03 19:59:19.240 8384-8399/ndk.example.com.ndkexample D/Native: fromNativeCall i=2,j=3
03-03 19:59:19.240 8384-8398/ndk.example.com.ndkexample I/Native: tid=1073839144 i=1 j=2
03-03 19:59:19.240 8384-8398/ndk.example.com.ndkexample D/Native: fromNativeCall i=1,j=2
03-03 19:59:19.240 8384-8406/ndk.example.com.ndkexample I/Native: tid=1074649768 i=9 j=10
03-03 19:59:19.240 8384-8406/ndk.example.com.ndkexample D/Native: fromNativeCall i=9,j=10
03-03 19:59:19.240 8384-8397/ndk.example.com.ndkexample I/Native: tid=1073838560 i=0 j=1
03-03 19:59:19.240 8384-8397/ndk.example.com.ndkexample D/Native: fromNativeCall i=0,j=1
03-03 19:59:19.240 8384-8384/ndk.example.com.ndkexample I/Native: main thread exit.....

发布了64 篇原创文章 · 获赞 42 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/tyyj90/article/details/86710758