We know that the Android system kernel is implemented using Linux, so the native code of JNI implementation in Android OS actually uses Linux threads, which requires the support of PThreads.

1. Pthread method is involved

In Linux, C is used to develop multithreaded programs. Multithreading in Linux follows POSIX thread interface, called PThread.

The header file

#include<pthread.h>

1.1 pthread_create

Pthread_create is a thread-creating function in the UNIX environment

Function declaration

Int pthread_create(pthread_t* RESTRICT TIdp,const pthread_attr_t *restrict_attr,void* (* start_rTN)(void*),void *restrict TIDP,const pthread_attr_t *restrict_attr,void* (*start_rtn)(void*),void *restrict arg);Copy the code

When success is returned, the memory unit pointed to by tiDP is set to the thread ID of the newly created thread. The attr argument is used to specify various thread attributes. The newly created thread starts at the address of the start_rtn function, which has only one universal pointer argument, arg. If you need to pass more than one argument to start_rtn, you need to put those arguments into a structure and pass the address of that structure as an argument to the ARG.

A pointer that is qualified by RESTRICT is initially the only method that can access the object to which the pointer points. Objects can be accessed only if the second pointer is based on the first. Access to objects is restricted to pointer expressions based on RESTRICT. Pointers qualified by RESTRICT are used primarily for function parameters or to point to memory allocated by malloc(). Restrict data types do not change the semantics of the program. The compiler can better optimize certain types of routines by making the assumption that restrict qualified Pointers are the only way to access objects.

The return value

Returns 0 on success, error number otherwise.

parameter

The first argument is a pointer to the thread identifier.

The second parameter sets the thread properties.

The third argument is the starting address from which the thread runs the function.

The last argument is the argument to run the function.

1.2 pthread_join

The pthread_join function is used to wait for the end of a thread, synchronizing operations between threads.

Function declaration

int pthread_join(pthread_t thread, void **retval);
Copy the code

The pthread_join() function blocks until the thread specified by thread terminates. When the function returns, the resource held by the waiting thread is retrieved. If the thread is terminated, the function returns immediately. The thread specified by thread must be joinable.

The return value

Returns 0 on success, error number otherwise.

parameter

Thread: A thread ID that identifies a unique thread.

Retval: user-defined pointer used to store the return value of the thread being waited for.

1.3 pthread_self

Pthread_self is a function that obtains the ID of the thread itself.

Function declaration

pthread_t pthread_self(void);
Copy the code

Gets the ID of the thread itself. Pthread_t is of type unsigned long int, so use %lu when printing otherwise it will display wrong.

The return value

Pthread_t is of type unsigned long int

1.4 pthread_exit

The thread terminates execution by calling the pthread_exit function, just as the process calls exit when it terminates.

Function declaration

void pthread_exit(void* retval);
Copy the code

This function terminates the calling thread and returns a pointer to an object.

Two, involving JNI method

2.1 AttachCurrentThread

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

Attach the current thread to the Java VM. Returns the JNI interface pointer in the JNIEnv argument.

Attempting to attach already attached thread is a no-operation.

Local threads cannot connect to two Java VMS at the same time.

The context classloader is the boot loader when the thread attaches to the VM.

LINKAGE:

Index 4 in the JavaVM interface function table.

PARAMETERS:

Vm: VM to which the current thread will attach.

P_env: pointer to the JNI interface pointer of the current thread.

Thr_args: Can be NULL or a pointer to the JavaVMAttachArgs structure to specify additional information:

As of JDK/JRE 1.1, the second argument to AttachCurrentThread is always a pointer to JNIEnv. The third parameter of AttachCurrentThread is reserved and should be set to NULL.

Starting with JDK/JRE 1.2, NULL is passed as the third argument, in line with 1.1, or a pointer to the following structure to specify additional information:

typedef struct JavaVMAttachArgs { jint version; /* Must be at least JNI_VERSION_1_2 */ char *name; /* The thread name uses the modified UTF-8 string, or NULL */ jobject group; /* Global reference to a ThreadGroup object, or NULL */} JavaVMAttachArgsCopy the code

RETURNS:

Return JNI_OK on success; Returns the appropriate JNI error code (negative) on failure.

2.2 DetachCurrentThread

jint DetachCurrentThread(JavaVM *vm);
Copy the code

Separate the current thread from the Java VM. All Java monitors held by the thread will be released. All Java threads waiting for this thread to die are notified.

Starting with JDK/JRE 1.2, the main thread can be separated from the VM.

LINKAGE:

Index 5 in the JavaVM interface function table.

PARAMETERS:

Vm: Vm from which the current thread will detach.

RETURNS:

Return JNI_OK on success; Returns the appropriate JNI error code (negative) on failure.

Use local threads

The JNI interface pointer (JNIEnv) is only valid in the current thread. If another thread needs to access the Java VM, it must first call AttachCurrentThread() to connect itself to the VM and get the JNI interface pointer. Once connected to the VM, the local thread works just like a normal Java thread running in a local method. The local thread remains connected to the VM until it calls DetachCurrentThread() to detach itself.

Connected threads should have enough stack space to perform a reasonable amount of work. The stack space allocation per thread is operating system specific. For example, with pThreads, you can specify the stack size in the pthread_attr_t parameter of pthread_CREATE.

The local thread connected to the VM must call DetachCurrentThread() to detach itself before exiting. If there are Java methods on the call stack, the thread cannot detach.

The following code snippet is Java code running on 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 (" Start 10 Native threads...") ); 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); }}Copy the code

The local code is as follows:

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

We created 10 local threads in the local method mainThread. Each thread executed the local method thread_fun and called AttachCurrentThread to retrieve the JNIEnv. FromNativeCall (int I, int j);} fromNativeCall(int I, int j); Finally, DetachCurrentThread is called to remove Attach from the VM and detach the thread from the VM.

Of course, multiple arguments are passed to the executing function when the thread is created, so a custom structure pointer is passed. And after all threads are created, execute pthread_JOIN to wait. Also promote the jniCallObj passed in mainThread as a global reference and use env->GetJavaVM() to get the JavaVM global reference.

Finally, the results:

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: the main thread exit...Copy the code