linux线程是由进程模拟,和进程没有什么本质上的区别,相比于进程,线程在使用上便利很多,线程之间可以共享数据,但这也带来了一系列的问题。在我们在一个线程中对一个数据进行操作时,有时不希望别的线程修改数据,因此锁就诞生了,把资源进行上锁和解锁,被上锁的资源,在别的线程想要访问时,将不能访问,根据逻辑处理,一般情况下会进入阻塞状态(等待),被称为线程同步
线程的锁的种类有互斥锁、读写锁、条件变量、自旋锁、信号灯。实际开发中只需要会玩互斥锁就够了
这边在CentOS中创建一个c文件,其中创建两个线程,分别对一个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));
pthread_create(&pthid,0,thread_start,(void *)1);
pthread_create(&pthid,0,thread_start1,(void *)2);
usleep(20000);
}
thread_start中对num进行++操作,thread_start1中打印num,并做++处理后再打印num,结果如下:
我们预想的是num1和num2是连续的,不想让其他线程影响,所以需要用到线程锁,修改后的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));
pthread_mutex_init(&foo_mutex, NULL);
pthread_create(&pthid,0,thread_start,(void *)1);
pthread_create(&pthid,0,thread_start1,(void *)2);
usleep(20000);
pthread_mutex_destroy(&foo_mutex);
}
执行结果如下:num1和num2是连续的数字
JNI创建线程
在JNI中,我们在java层调用native方法,是在一个线程中的,所以主线程中调用native方法,如果耗时严重,有必要在native层使用多线程,下面介绍native中使用多线程的方法
首先新建一个Java类
package com.aruba.ndkapplication;
import android.util.Log;
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层调用");
destroy();
}
}
在c++中编写相应的方法,由于一个应用对应一个JVM,一个线程对应一个ENV,所以JNI中使用线程比较特殊,需要通过AttachCurrentThread先将线程添加到JVM,得到对应的ENV,并且子线程中得到的ENV不能使用FindClass方法获取非系统class,通过AttachCurrentThread附加到虚拟机的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类,如自己在java层定义的类会返回NULL。所以我们这边使用java的setEnv方法调用native层,保存一个全局的jobject
#include <pthread.h>
pthread_t pthid;
JavaVM *vm;
jobject g_obj;
void *thread_start(void *arg) {
LOGI("thread_start begin");
JNIEnv *env;
if (vm->AttachCurrentThread(&env, NULL) != JNI_OK) {
LOGI("%s AttachCurrentThread error failed ", __FUNCTION__);
return NULL;
}
sleep(3);
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");
pthread_create(&pthid, 0, thread_start, (void *) 1);
}
JNIEXPORT void JNICALL
native_set_env(JNIEnv *env, jobject jobj) {
if (vm != NULL)
vm = NULL;
env->GetJavaVM(&vm);
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
}
};
static int registerNativesThread(JNIEnv *env) {
LOGI("registerNatives begin");
jclass clazz;
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;
}
在java中调用
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();
}
});
点击按钮后,我们查看logcat
其中native_startThread begin打印在主线程,另外两个都在子线程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!