The JVM can execute multiple threads in the same address space. Because multiple threads may share resources at the same time, this increases the complexity of the program.

First, prepare knowledge

1.1 Restrictions

If your native code is running in multiple threads, there are some constraints to be aware of so that your native code is running in no matter how many threads.

  • The JNIEnv pointer is valid only in the thread it is in and cannot be passed and used across threads. When different threads call a local method, the JNIEnv pointer is passed in differently.
  • Local references are valid only in the thread in which they were created, and again cannot be passed across threads. However, local references can be converted to global references for use by multiple threads.

1.2 API

1.2.1 MonitorEnter

jint MonitorEnter(JNIEnv *env, jobject obj);
Copy the code

Enter the monitor associated with the underlying Java object referenced by OBJ.

Each Java object has a monitor associated with it. If the current thread already owns a monitor associated with OBJ, it increases the counter in the monitor indicating the number of times this thread has entered the monitor. If the monitor associated with OBJ does not belong to any thread, the current thread becomes the owner of the monitor and sets the count of entries for this monitor to 1. If another thread already owns the monitor associated with OBJ, the current thread will wait until the monitor is released, and then try again to take ownership.

A monitor entered through the MonitorEnter JNI function call cannot be returned to exit using the Monitorexit Java virtual machine directive or the synchronized method. MonitorEnter JNI function calls and MonitorEnter Java virtual machine directives may compete to enter monitors associated with the same object.

To avoid deadlocks, the MonitorExit JNI call must be used to exit the monitor entered through the MonitorEnter JNI function call, unless the DetachCurrentThread call is used to implicitly release the JNI monitor.

LINKAGE:

Index 217 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

Obj: Plain Java object or class object.

RETURNS:

Returns “0” on success; Returns a negative value on failure.

1.2.2 MonitorExit

jint MonitorExit(JNIEnv *env, jobject obj);
Copy the code

The current thread must be the owner of the monitor associated with the underlying Java object referenced by OBJ. A thread decrement counter indicating how many times it has entered the monitor. If the value of the counter becomes zero, the current thread releases the monitor.

Native code may not use MonitorExit to exit a monitor entered through the synchronized method or monitorenter Java virtual machine directive.

LINKAGE:

Index 218 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

Obj: Plain Java object or class object.

RETURNS:

Returns “0” on success; Returns a negative value on failure.

EXCEPTIONS:

Does not have any monitor IllegalMonitorStateException if the current thread.

1.3 Monitor inlet and outlet

The monitor is the basic synchronization mechanism of the JAVA platform. Each object can be bound to a monitor:

synchronized (obj) {

     ...  // synchronized block

 }
Copy the code

The synchronization equivalent of the JAVA code described above can be achieved in native code by calling JNI functions. This uses two JNI functions: MonitorEnter, which enters the synchronized block, and MonitorExit, which functions the synchronized block.

if ((*env)->MonitorEnter(env, obj) ! = JNI_OK) { ... /* error handling */ } ... /* synchronized block */ if ((*env)->MonitorExit(env, obj) ! = JNI_OK) { ... /* error handling */ };Copy the code

To run the above code, the thread must enter obJ’s monitor before executing the code in the synchronized block. MonitorEnter needs to pass in jobject as a parameter. Also, if another thread has entered the jobject monitor, the current thread will block. If the current thread in does not have a monitor with MonitorExit, will produce an error, and throw a IllegalMonitorStateException anomalies. The above code contains calls to MonitorEnter and MonitorExit. When using the MonitorEnter and MonitorExit, it is important to pay attention to error checking because these functions may fail (for example, the resource allocation to set up the monitor failed, etc.). This pair of functions can work on types such as JClass, JString, and jarray, all of which have the common feature that they are special types referenced by Jobject.

If there is a MonitorEnter method, there must be a MonitorExit method as well. Be especially careful where there are errors or exceptions to handle.

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) ... ;Copy the code

Calling MonitorEnter without MonitorExit is likely to cause a deadlock.

Two, the actual use

This code is based on the Android platform. A normal Java method and a Native method are defined, both of which increment the MODIFY field. We started 10 threads in Java that called modify() and nativeModify(). Since the native code was not synchronized, the results were definitely not what we wanted.

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 (" Start 10 threads...") ); 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(); }Copy the code

Native code:

#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__); }; * /}Copy the code

Running results:

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

The visibility of the Modify field in the visible Java object is no longer guaranteed. This requires the JNI local implementation to be synchronized as well.

Open comments in the native code. The result is already what we want it to be.

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

By comparison, we actually know that synchronization in JAVA is much more convenient than in JNI Native, so try to use JAVA for synchronization and move all synchronization related code to JAVA.