【JNI编程】JNI中进行线程同步

JVM可以做到在相同的地址空间内执行多个线程。由于多个线程可能会在同时共享资源,所以,增加了程序的复杂性。

一、预备知识

1.1 约束限制

如果你的本地代码要运行在多个线程中,有一些约束条件需要注意,这样的话,才能使得你的本地代码无论被多少个线程同时运行,都不会出现问题。

  • JNIEnv指针只在它所在的线程中有效,不能跨线程传递和使用。不同线程调用一个本地方法时,传入的JNIEnv指针是不同的。

  • 局部引用只在创建它们的线程中有效,同样不能跨线程传递。但可以把局部引用转化成全局引用来供多线程使用。

1.2 API

1.2.1 MonitorEnter

jint MonitorEnter(JNIEnv *env, jobject obj);

进入与obj引用的底层Java对象相关联的监视器。

每个Java对象都有一个与之关联的监视器。如果当前线程已经拥有与obj关联的监视器,它会增加监视器中的计数器,指示此线程进入监视器的次数。 如果与obj关联的监视器不属于任何线程,则当前线程将成为监视器的所有者,将此监视器的条目计数设置为1。如果另一个线程已拥有与obj关联的监视器,则当前线程将等待,直到监视器被释放,然后再次尝试获得所有权。

通过MonitorEnter JNI函数调用进入的监视器无法使用monitorexit Java虚拟机指令或synchronized方法返回退出。 MonitorEnter JNI函数调用和monitorenter Java虚拟机指令可能竞争进入与同一对象关联的监视器。

为避免死锁,必须使用MonitorExit JNI调用退出通过MonitorEnter JNI函数调用进入的监视器,除非使用DetachCurrentThread调用来隐式释放JNI监视器。

LINKAGE:

JNIEnv接口函数表中的索引217。

PARAMETERS:

env:JNI接口指针。

obj:普通的Java对象或类对象。

RETURNS:

成功返回“0”;失败时返回负值。

1.2.2 MonitorExit

jint MonitorExit(JNIEnv *env, jobject obj);

当前线程必须是与obj引用的底层Java对象关联的监视器的所有者。线程递减计数器,指示它进入此监视器的次数。如果计数器的值变为零,则当前线程释放监视器。

本地代码不得使用MonitorExit退出通过synchronized方法或monitorenter Java虚拟机指令进入的监视器。

LINKAGE:

JNIEnv接口函数表中的索引218。

PARAMETERS:

env:JNI接口指针。

obj:普通的Java对象或类对象。

RETURNS:

成功时返回“0”; 失败时返回负值。

EXCEPTIONS:

IllegalMonitorStateException: 如果当前线程不拥有监视器。

1.3 监视器的入口和出口

监视器是JAVA平台的基本同步机制。每一个对象都可以和一个监视器绑定:

synchronized (obj) {

     ...  // synchronized block

 }

本地代码中可以通过调用JNI函数来达到与上述JAVA代码中等效的同步目的。这要用到两个JNI函数:MonitorEnter负责进入同步块,MonitorExit用来函数同步块。

if ((*env)->MonitorEnter(env, obj) != JNI_OK) {

     ... /* error handling */

 }

 ...  /* synchronized block */

 if ((*env)->MonitorExit(env, obj) != JNI_OK) {

     ... /* error handling */

 };

运行上面这段代码时,线程必须先进入obj的监视器,再执行同步块中的代码。MonitorEnter需要传入jobject作为参数。同时,如果另一个线程已经进入了这个与jobject监视器的话,当前线程会阻塞。如果当前线程在不拥有监视器的情况下调用MonitorExit的话,会产生一个错误,并抛出一个IllegalMonitorStateException异常。上面的代码中包含了MonitorEnter和MonitorExit这对函数的调用,在这对函数的使用时,我们一定要注意错误检查,因为这对函数有可能执行失败(比如,建立监视器的资源分配不成功等原因)。这对函数可以工作在jclass、jstring、jarray等类型上面,这些类型的共同特征是,都是jobject引用的特殊类型。

有一个MonitorEnter方法,一定也要有一个与之对应的MonitorExit方法。尤其是在有错误或者异常需要处理的地方,要尤其小心。

if ((*env)->MonitorEnter(env, obj) != JNI_OK) ...;

 ...

 if ((*env)->ExceptionOccurred(env)) {

     ... /* exception handling */

     /* remember to call MonitorExit here */
     if ((*env)->MonitorExit(env, obj) != JNI_OK) ...;

 }

 ... /* Normal execution path.

 if ((*env)->MonitorExit(env, obj) != JNI_OK) ...;

调用MonitorEnter而不调用MonitorExit的话,很可能会引起死锁。

二、实际使用

本代码基于Android平台。其中定义了一个普通Java方法和一个Native方法,这两个方法均对modify字段进行自增。我们在Java中启动10条线程,线程内调用方法modify()和nativeModify(),由于本地代码没有进行同步,运行结果一定不是我们想要的。

package ndk.example.com.ndkexample;

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

public class MainActivity extends AppCompatActivity {

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

    private int modify = 0;

    @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条线程...");
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    modify();
                    nativeModify();
                }
            }).start();
        }
    }

    private void modify() {
        synchronized (this) {
            modify++;
            Log.d("Java", "modify=" + modify);
        }
    }

    public native void nativeModify();

}

Native代码:

#include <jni.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__))

extern "C"
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeModify(JNIEnv *env, jobject instance) {
    jclass cls = env->GetObjectClass(instance);
    jfieldID fieldID = env->GetFieldID(cls, "modify", "I");

    /*if (env->MonitorEnter(instance) != JNI_OK) {
        LOGE("%s: MonitorEnter() failed", __FUNCTION__);
    }*/

    /* synchronized block */
    int val = env->GetIntField(instance, fieldID);
    val++;
    LOGI("modify=%d", val);
    env->SetIntField(instance, fieldID, val);

    /*if (env->ExceptionOccurred()) {
        LOGE("ExceptionOccurred()...");
        if (env->MonitorExit(instance) != JNI_OK) {
            LOGE("%s: MonitorExit() failed", __FUNCTION__);
        };
    }

    if (env->MonitorExit(instance) != JNI_OK) {
        LOGE("%s: MonitorExit() failed", __FUNCTION__);
    };*/

}

运行结果

01-30 10:50:15.211 13844-13876/ndk.example.com.ndkexample D/Java: modify=1
01-30 10:50:15.211 13844-13871/ndk.example.com.ndkexample D/Java: modify=2
01-30 10:50:15.211 13844-13876/ndk.example.com.ndkexample I/Native: modify=3
01-30 10:50:15.211 13844-13871/ndk.example.com.ndkexample I/Native: modify=3
01-30 10:50:15.213 13844-13872/ndk.example.com.ndkexample D/Java: modify=4
01-30 10:50:15.213 13844-13872/ndk.example.com.ndkexample I/Native: modify=5
01-30 10:50:15.213 13844-13875/ndk.example.com.ndkexample D/Java: modify=6
01-30 10:50:15.213 13844-13874/ndk.example.com.ndkexample D/Java: modify=7
01-30 10:50:15.213 13844-13875/ndk.example.com.ndkexample I/Native: modify=7
01-30 10:50:15.213 13844-13874/ndk.example.com.ndkexample I/Native: modify=8
01-30 10:50:15.213 13844-13869/ndk.example.com.ndkexample D/Java: modify=9
01-30 10:50:15.213 13844-13869/ndk.example.com.ndkexample I/Native: modify=10
01-30 10:50:15.213 13844-13868/ndk.example.com.ndkexample D/Java: modify=10
01-30 10:50:15.213 13844-13868/ndk.example.com.ndkexample I/Native: modify=11
01-30 10:50:15.213 13844-13867/ndk.example.com.ndkexample D/Java: modify=12
01-30 10:50:15.213 13844-13867/ndk.example.com.ndkexample I/Native: modify=13
01-30 10:50:15.213 13844-13873/ndk.example.com.ndkexample D/Java: modify=14
01-30 10:50:15.213 13844-13873/ndk.example.com.ndkexample I/Native: modify=15
01-30 10:50:15.214 13844-13870/ndk.example.com.ndkexample D/Java: modify=16
01-30 10:50:15.214 13844-13870/ndk.example.com.ndkexample I/Native: modify=17

可见程序Java对象中的modify字段可见性无法保证了。这就需要JNI本地实现也要同步。

将本地代码中注释打开即可。运行结果已经是我们想要的结果了。

01-30 10:56:01.110 14313-14336/ndk.example.com.ndkexample D/Java: modify=1
01-30 10:56:01.110 14313-14334/ndk.example.com.ndkexample D/Java: modify=2
01-30 10:56:01.110 14313-14334/ndk.example.com.ndkexample I/Native: modify=3
01-30 10:56:01.110 14313-14336/ndk.example.com.ndkexample I/Native: modify=4
01-30 10:56:01.110 14313-14335/ndk.example.com.ndkexample D/Java: modify=5
01-30 10:56:01.111 14313-14335/ndk.example.com.ndkexample I/Native: modify=6
01-30 10:56:01.111 14313-14337/ndk.example.com.ndkexample D/Java: modify=7
01-30 10:56:01.111 14313-14337/ndk.example.com.ndkexample I/Native: modify=8
01-30 10:56:01.111 14313-14340/ndk.example.com.ndkexample D/Java: modify=9
01-30 10:56:01.111 14313-14340/ndk.example.com.ndkexample I/Native: modify=10
01-30 10:56:01.111 14313-14342/ndk.example.com.ndkexample D/Java: modify=11
01-30 10:56:01.111 14313-14342/ndk.example.com.ndkexample I/Native: modify=12
01-30 10:56:01.111 14313-14338/ndk.example.com.ndkexample D/Java: modify=13
01-30 10:56:01.113 14313-14339/ndk.example.com.ndkexample D/Java: modify=14
01-30 10:56:01.114 14313-14343/ndk.example.com.ndkexample D/Java: modify=15
01-30 10:56:01.114 14313-14343/ndk.example.com.ndkexample I/Native: modify=16
01-30 10:56:01.114 14313-14338/ndk.example.com.ndkexample I/Native: modify=17
01-30 10:56:01.114 14313-14341/ndk.example.com.ndkexample D/Java: modify=18
01-30 10:56:01.114 14313-14339/ndk.example.com.ndkexample I/Native: modify=19
01-30 10:56:01.114 14313-14341/ndk.example.com.ndkexample I/Native: modify=20

通过比较,实际上我们就知道用JAVA来进行同步要比在JNI Native上方便的多,所以,尽量用JAVA来做同步,把与同步相关的代码都挪到JAVA中去。

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

猜你喜欢

转载自blog.csdn.net/tyyj90/article/details/86735632
JNI