NDK--C語言線程運(yùn)用及jni創(chuàng)建線程

關(guān)于linux中線程的知識:https://blog.csdn.net/wucz122140729/article/details/98588567

關(guān)于linux中線程同步的知識:https://blog.csdn.net/wucz122140729/article/details/98589012

linux線程是由進(jìn)程模擬,和進(jìn)程沒有什么本質(zhì)上的區(qū)別,相比于進(jìn)程,線程在使用上便利很多,線程之間可以共享數(shù)據(jù),但這也帶來了一系列的問題。在我們在一個(gè)線程中對一個(gè)數(shù)據(jù)進(jìn)行操作時(shí),有時(shí)不希望別的線程修改數(shù)據(jù),因此鎖就誕生了,把資源進(jìn)行上鎖和解鎖,被上鎖的資源,在別的線程想要訪問時(shí),將不能訪問,根據(jù)邏輯處理,一般情況下會進(jìn)入阻塞狀態(tài)(等待),被稱為線程同步

線程的鎖的種類有互斥鎖、讀寫鎖、條件變量、自旋鎖、信號燈。實(shí)際開發(fā)中只需要會玩互斥鎖就夠了
這邊在CentOS中創(chuàng)建一個(gè)c文件,其中創(chuàng)建兩個(gè)線程,分別對一個(gè)int變量做處理
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>

int num = 0;

pthread_t pthid;

void * thread_start(void *arg){
  while(1){

     num++;

     usleep(random()%200);
  }
}

void * thread_start1(void *arg){
  while(1){

     printf("num1 = %d\n",num);
     num++;
     usleep(random()%200);
     printf("num2 = %d\n",num);

     usleep(random()%200);
  }
}


int main(){
 
  srand(time(0)); 

  //創(chuàng)建線程1
  pthread_create(&pthid,0,thread_start,(void *)1);

  //創(chuàng)建線程2
  pthread_create(&pthid,0,thread_start1,(void *)2);

  usleep(20000);

}
thread_start中對num進(jìn)行++操作,thread_start1中打印num,并做++處理后再打印num,結(jié)果如下:
我們預(yù)想的是num1和num2是連續(xù)的,不想讓其他線程影響,所以需要用到線程鎖,修改后的c文件:
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>

int num = 0;

pthread_t pthid;

pthread_mutex_t foo_mutex;

void * thread_start(void *arg){
  while(1){

     //申請加鎖
     pthread_mutex_lock(&foo_mutex); 

     num++;

     //釋放鎖
     pthread_mutex_unlock(&foo_mutex);

     usleep(random()%200);
  }
}

void * thread_start1(void *arg){
  while(1){

     //申請加鎖
     pthread_mutex_lock(&foo_mutex);

     printf("num1 = %d\n",num);
     num++;
     usleep(random()%200);
     printf("num2 = %d\n",num);

     //釋放鎖
     pthread_mutex_unlock(&foo_mutex);

     usleep(random()%200);
  }
}


int main(){
 
  srand(time(0)); 
 
  //默認(rèn)鎖類型:當(dāng)一個(gè)線程加鎖以后,其余請求鎖的線程將形成一個(gè)等待隊(duì)列,并在解鎖后按優(yōu)先級獲得鎖
  pthread_mutex_init(&foo_mutex, NULL);

  //創(chuàng)建線程1
  pthread_create(&pthid,0,thread_start,(void *)1);

  //創(chuàng)建線程2
  pthread_create(&pthid,0,thread_start1,(void *)2);

  usleep(20000);
 
  //銷毀鎖
  pthread_mutex_destroy(&foo_mutex);
}
執(zhí)行結(jié)果如下:num1和num2是連續(xù)的數(shù)字

JNI創(chuàng)建線程

在JNI中,我們在java層調(diào)用native方法,是在一個(gè)線程中的,所以主線程中調(diào)用native方法,如果耗時(shí)嚴(yán)重,有必要在native層使用多線程,下面介紹native中使用多線程的方法
首先新建一個(gè)Java類
package com.aruba.ndkapplication;

import android.util.Log;

/**
 * Created by aruba on 2020/4/17.
 */
public class ThreadUtils {
    public static native void startThread();

    public native void setEnv();

    public static native void destroy();

    public static void getInfoFromC() {
        Log.i("ThreadUtils", "getInfoFromC方法被native層調(diào)用");
        destroy();
    }
}
在c++中編寫相應(yīng)的方法,由于一個(gè)應(yīng)用對應(yīng)一個(gè)JVM,一個(gè)線程對應(yīng)一個(gè)ENV,所以JNI中使用線程比較特殊,需要通過AttachCurrentThread先將線程添加到JVM,得到對應(yīng)的ENV,并且子線程中得到的ENV不能使用FindClass方法獲取非系統(tǒng)class,通過AttachCurrentThread附加到虛擬機(jī)的線程在查找類時(shí)只會通過系統(tǒng)類加載器進(jìn)行查找,不會通過應(yīng)用類加載器進(jìn)行查找,因此可以加載系統(tǒng)類,但是不能加載非系統(tǒng)類,如自己在java層定義的類會返回NULL。所以我們這邊使用java的setEnv方法調(diào)用native層,保存一個(gè)全局的jobject
//===================================多線程================================================

#include <pthread.h>

pthread_t pthid;
JavaVM *vm;
jobject g_obj;

//線程方法
void *thread_start(void *arg) {
    LOGI("thread_start begin");
    JNIEnv *env;
    //將線程添加到JVM中
    if (vm->AttachCurrentThread(&env, NULL) != JNI_OK) {
        LOGI("%s AttachCurrentThread error failed ", __FUNCTION__);
        return NULL;
    }

    sleep(3);
    //調(diào)用java中的getInfoFromC方法
    jclass clz = env->GetObjectClass(g_obj);
    jmethodID mid = env->GetStaticMethodID(clz, "getInfoFromC", "()V");
    env->CallStaticVoidMethod(clz, mid);
    
    pthread_exit(0);
}

//開啟線程
JNIEXPORT void JNICALL
native_startThread(JNIEnv *env, jclass type) {
    LOGI("native_startThread begin");

    //創(chuàng)建線程
    pthread_create(&pthid, 0, thread_start, (void *) 1);
}

//設(shè)置環(huán)境
JNIEXPORT void JNICALL
native_set_env(JNIEnv *env, jobject jobj) {
    if (vm != NULL)
        vm = NULL;

    env->GetJavaVM(&vm);
    //保持ThreadUtils對象
    g_obj = env->NewGlobalRef(jobj);
}


//銷毀資源
JNIEXPORT void JNICALL
native_destroy(JNIEnv *env, jclass type) {
    if (vm != NULL)
        vm = NULL;
    env->DeleteGlobalRef(g_obj);
}

//多線程
static const JNINativeMethod gMethodsThread[] = {
        {
                "startThread", "()V", (void *) native_startThread
        },
        {
                "setEnv",      "()V", (void *) native_set_env
        },
        {
                "destroy",     "()V", (void *) native_destroy
        }
};

//注冊多線程Method
static int registerNativesThread(JNIEnv *env) {
    LOGI("registerNatives begin");
    jclass clazz;
    //找到j(luò)ava的類
    clazz = env->FindClass("com/aruba/ndkapplication/ThreadUtils");

    if (clazz == NULL) {
        LOGI("clazz is null");
        return JNI_FALSE;
    }

    if (env->RegisterNatives(clazz, gMethodsThread, NELEM(gMethodsThread)) < 0) {
        LOGI("RegisterNatives error");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

//===================================多線程end================================================

在java中調(diào)用
        Button btn_click3 = findViewById(R.id.btn_click3);
        btn_click3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ThreadUtils threadUtils = new ThreadUtils();
                threadUtils.setEnv();
                threadUtils.startThread();
            }
        });
點(diǎn)擊按鈕后,我們查看logcat
其中native_startThread begin打印在主線程,另外兩個(gè)都在子線程
demo地址:https://gitee.com/aruba/NDKApplication.git
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容