自定义线程实现c++代码回调run方法

news/2024/10/22 7:15:12/

目录

pthread_create函数介绍


前面写过一篇文章《Thread类的start()方法创建线程的底层分析》,这次来自定义一个线程,并实现在底层创建内核线程来执行用户代码。

pthread_create函数介绍

在这之前,先熟悉下Linux中创建内核线程函数pthread_create的用法。

 pthread_create函数的第一个参数用来接收新创建的线程ID,该参数的类型是pthread_t。如何查看这个类型的定义?在Linux中查找pthread.h

[root]# whereis pthread.h
pthread: /usr/include/pthread.h

 打开pthread.h,发现其引入了pthreadtypes.h

#ifndef _PTHREAD_H
#define _PTHREAD_H      1#include <features.h>
#include <endian.h>
#include <sched.h>
#include <time.h>#include <bits/pthreadtypes.h>
#include <bits/setjmp.h>
#include <bits/wordsize.h>

打开即可看到pthread_t的定义

typedef unsigned long int pthread_t;

第二个参数用来设置线程属性,可以传空。

第三个参数是线程运行函数的起始地址。

最后一个参数是运行函数的参数。

了解了使用方法,编写如下MyThread.c代码。在main方法中创建新线程,然后在main和java_start方法中循环打印。

#include <pthread.h>
#include <stdio.h>void *java_start(void *arg) {while (1){usleep(1000000);printf("this is java_start method!\n");}} int main() {pthread_t tid;pthread_create(&tid, NULL, java_start, NULL);while (1){usleep(1000000);printf("this is main method!\n");}return 0;
}

使用如下命令进行编译:

gcc Mythread.c -o MyThread -pthread

 运行结果如下:

[root@192 yangxianzhu]# ./MyThread 
this is java_start method!
this is main method!
this is java_start method!
this is main method!
this is java_start method!
this is main method!
this is java_start method!
this is main method!
this is main method!
this is java_start method!
this is main method!
this is java_start method!

至此,就完成了在Linux创建一个新的内核线程。

自定义线程

接下来就开始自定义线程。仿照Thread.java,自定义线程MyThread.java代码如下:

package org.example;public class MyThread {public void start() {start0();}private native void start0();}

其中start0是一个native方法,使用命令javah生成对应的头文件。注意,先进入编译输出目录

 然后执行命令:javah org.example.MyThread,生成的头文件名为org_example_MyThread.h,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_example_MyThread */#ifndef _Included_org_example_MyThread
#define _Included_org_example_MyThread
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     org_example_MyThread* Method:    start0* Signature: ()V*/
JNIEXPORT void JNICALL Java_org_example_MyThread_start0(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif

为了方便后面编译,去除包名。将文件名改成MyThread.h,将里面的方法名JNICALL Java_org_example_MyThread_start0改成JNICALL Java_MyThread_start0。这样,MyThread.java对应的头文件就创建好了。

接下来修改MyThread.c如下:

#include <pthread.h>
#include <stdio.h>
#include "MyThread.h"void *java_start(void *arg) {while (1){usleep(1000000);printf("this is java_start method!\n");}}JNIEXPORT void JNICALL Java_MyThread_start0(JNIEnv *env, jobject obj) {pthread_t tid;pthread_create(&tid, NULL, java_start, NULL);while (1){usleep(1000000);printf("this is start0 method!\n");}
}int main() {return 0;
}

首先引入上面生成的头文件MyThread.h,注意这里使用的是#include "MyThread.h",表示该头文件与mythread.c在同一目录下。接着实现Java_MyThread_start0方法,将原先放到main方法里的创建线程的逻辑挪过去。

下面先验证在Java中通过JNI调用mythread.c中的Java_MyThread_start0方法。先将MyThread.c打包成库文件mythread.so,将MyThread.c和MyThread.h拷贝到同一个目录下

[root@192 yangxianzhu]# pwd
/home/yangxianzhu
[root@192 yangxianzhu]# ll
总用量 8
-rw-r--r--. 1 root root 494 12月  4 15:22 MyThread.c
-rw-r--r--. 1 root root 438 12月  4 14:48 MyThread.h

由于在MyThread.h中引入了jni.h,该文件位于jdk下的include目录。打包命令如下:

[root@192 yangxianzhu]# gcc -fPIC -I /usr/local/java/jdk1.8.0_341/include -I /usr/local/java/jdk1.8.0_341/include/linux -shared -o MyThread.so MyThread.c
[root@192 yangxianzhu]# ll
总用量 16
-rw-r--r--. 1 root root  494 12月  4 15:22 MyThread.c
-rw-r--r--. 1 root root  438 12月  4 14:48 MyThread.h
-rwxr-xr-x. 1 root root 8184 12月  4 15:32 MyThread.so

MyThread.so就是生成的库文件,接着在MyThread.java中通过System.load引入库文件,然后创建MyThread对象并调用start方法进行测试。

package org.example;public class MyThread {static {//加载库文件System.load("/home/yangxianzhu/MyThread.so");}public void start() {start0();}private native void start0();public static void main(String[] args) {new MyThread().start();}
}

为了方便编译和运行,将MyThread.java中的包路径去除,如下:

public class MyThread {static {//加载库文件System.load("/home/yangxianzhu/MyThread.so");}public void start() {start0();}private native void start0();public static void main(String[] args) {new MyThread().start();}
}

然后将其拷贝到Linux中的任意目录,此处放到跟库文件同样的/home/yangxianzhu下

[root@192 yangxianzhu]# ll
总用量 20
-rw-r--r--. 1 root root  494 12月  4 15:22 MyThread.c
-rw-r--r--. 1 root root  438 12月  4 14:48 MyThread.h
-rw-r--r--. 1 root root  311 12月  4 15:37 MyThread.java
-rwxr-xr-x. 1 root root 8184 12月  4 15:32 MyThread.so

使用javac命令进行编译:

[root@192 yangxianzhu]# javac MyThread.java 
[root@192 yangxianzhu]# ll
总用量 24
-rw-r--r--. 1 root root  494 12月  4 15:22 MyThread.c
-rw-r--r--. 1 root root  529 12月  4 15:41 MyThread.class
-rw-r--r--. 1 root root  438 12月  4 14:48 MyThread.h
-rw-r--r--. 1 root root  311 12月  4 15:37 MyThread.java
-rwxr-xr-x. 1 root root 8184 12月  4 15:32 MyThread.so

然后使用java命令运行,结果如下:

[root@192 yangxianzhu]# java MyThread
this is java_start method!
this is start0 method!
this is java_start method!
this is start0 method!
this is java_start method!
this is start0 method!
this is java_start method!
this is start0 method!

至此就完成了在Java中通过JNI调用MyThread.c中的创建内核线程的方法。

下面就是如何在MyThread.c中回调MyThread.java中的方法,在MyThread.java中添加run方法

public class MyThread {static {//加载库文件System.load("/home/yangxianzhu/MyThread.so");}public void start() {start0();}private native void start0();public void run() {System.out.println("this is run method");}public static void main(String[] args) {new MyThread().start();}
}

修改MyThread.c内容如下:

#include <pthread.h>
#include <stdio.h>
#include "MyThread.h"JavaVM *javaVM;
int status; //线程状态:0|INITIALIZED、1|RUNNABLEvoid *java_start(void *arg) {JNIEnv *env=NULL;(*javaVM)->AttachCurrentThread(javaVM, &env, NULL);jclass cls;jobject obj;jmethodID cid;jmethodID rid;jint ret=0;//通过虚拟机找到Java中的类cls=(*env)->FindClass(env, "MyThread");if (cls==NULL){printf("class is not found\n");return;}//如果找到该类,此时通过无参构造函数new出来cid=(*env)->GetMethodID(env, cls, "<init>", "()V");if(cid==NULL) {printf("constructor is not found\n");}//通过构造函数实例化obj=(*env)->NewObject(env, cls, cid);if(obj==NULL) {printf("NewObject is error\n");}//找到run方法rid=(*env)->GetMethodID(env, cls, "run", "()V");if (rid==NULL){printf("run method id not found\n");}//直到线程状态不为0|INITIALIZED,才会执行下面的run方法while (status == 0){usleep(1000000);printf("thread state is INITIALIZED\n");}printf("thread state is RUNNABLE\n");//开始调用run方法(*env)->CallIntMethod(env, obj, rid, NULL);
}JNIEXPORT void JNICALL Java_MyThread_start0(JNIEnv *env, jobject obj) {pthread_t tid;pthread_create(&tid, NULL, java_start, NULL);//休眠5秒后,将线程状态改成1|RUNNABLEusleep(5000000);status=1;
}//JVM启动的时候调用,对javaVM进行赋值
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {javaVM=vm;return JNI_VERSION_1_8;
}int main() {return 0;
}

重新生成库文件,然后再次编译并运行MyThread,结果如下:

[root@192 yangxianzhu]# java MyThread
thread state is INITIALIZED
thread state is INITIALIZED
thread state is INITIALIZED
thread state is INITIALIZED
thread state is INITIALIZED
thread state is RUNNABLE
this is run method

可以看出MyThread.java中的run方法被执行了。

至此就完成了在Java中通过JNI技术调用.C中的代码创建新内核线程,然后在内核线程中回调Java中的方法。


http://www.ppmy.cn/news/1522.html

相关文章

GPS卫星位置解算

本文介绍了基于C语言的GPS卫星位置解算原理与程序设计。针对每个原理、公式、代码设计进行了详细讲解&#xff0c;希望能够给测绘学子们带来帮助。 参考书籍&#xff1a; 李征航 黄劲松&#xff1a;GPS测量与数据处理&#xff08;第三版&#xff09; 目录 基础原理 1&#xf…

9.Springboot整合Security很全

1.什么是Security SpringSecurity是基于Spring AOP和Servlet过滤器的安全框架。 它提供全面的安全性解决方案&#xff0c;同时在Web 请求级和方法调用级处理身份确认和授权。 2.Spring Security核心功能&#xff1f; &#xff08;1&#xff09;认证&#xff08;你是谁&…

【Redis】Redis安装步骤和特性以及支持的10种数据类型(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

【计算机视觉】图像形成与颜色

图像形成与颜色 光照及阴影 辐射度学 颜色 颜色信息反映了入射光的能量分布与波长&#xff0c;可见光的波长在400nm到760nm之间。 RGB RGB分别代表三个基色&#xff08;R-红色、G-绿色、B-蓝色&#xff09;&#xff0c;如(0,0,0)表示黑色、(255, 255, 255)表示白色。其中2…

Kafka - 主题Topic与消费者消息Offset日志记录机制

Kafka Topic 可以根据业务类型&#xff0c;分发到不同的Topic中&#xff0c;对于每一个Topic&#xff0c;下面可以有多个分区(Partition)日志文件: kafka 下的Topic的多个分区&#xff0c;每一个分区实质上就是一个队列&#xff0c;将接收到的消息暂时存储到队列中&#xff0…

计算机毕业设计Java大众采编本微资讯发布平台(源码+系统+mysql数据库+lw文档)

计算机毕业设计Java大众采编本微资讯发布平台(源码系统mysql数据库lw文档) 计算机毕业设计Java大众采编本微资讯发布平台(源码系统mysql数据库lw文档)本源码技术栈&#xff1a; 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言 开发软件&#xff1a;idea eclipse…

大数据必学Java基础(一百一十一):过滤器注解应用和开发案例

文章目录 过滤器注解应用和开发案例 一、过滤器注解应用 二、开发案例

windows域控上批量修改域账号密码

目录 一、查询密码过期域账号信息 &#xff08;一&#xff09;根据OU组织架构查询密码过期账号 &#xff08;二&#xff09;查询域控所有密码过期账号 &#xff08;三&#xff09;导出dsquery查询的信息 二、批量修改过期域账号密码 &#xff08;一&#xff09;根据dsque…