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

news2024/12/27 13:05:10

目录

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|RUNNABLE

void *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|RUNNABLE
    usleep(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.coloradmin.cn/o/61177.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机视觉】 摄像机标定

摄像机标定 齐次坐标 齐次坐标&#xff0c;将欧氏空间的无穷远点&#xff0c;与投影空间中有实际意义的消失点&#xff0c;建立起映射关系。 把齐次坐标转化为笛卡尔坐标的方法&#xff1a;是前面n-1个坐标分量分别除以最后一个分量即可 一些解释和性质&#xff1a; 比较好的…

Linux最常用命令用法总结(精选)

1. su 普通用户切换root用户 ubuntuubuntu20:~$ su Password: rootubuntu20:/home/ubuntu# exit exit ubuntuubuntu20:~$ 2. clear 清除当前终端显示的输出快捷键ctrlL键 3. cd 改变目录 ubuntuubuntu20:~/workspace$ cd .. ubuntuubuntu20:~$ cd / ubuntuubuntu20:/$ c…

docker下搭建redis集群

1. 环境准备 准备好Linux系统机器&#xff0c;并安装好docker&#xff0c;阅读这篇文章前请先了解清楚docker的基本知识并且会熟悉运用docker的常用命令。学习docker基础知识可以参考这篇博文 安装好并启动docker后就可以开始搭建redis了 2. docker容器下安装redis 本篇文章…

数据链路层(必备知识)

文章目录1、数据链路层的作用2、认识以太网<1>以太网帧格式<2>认识MAC地址<3>认识MTU<4>查看硬件地址和MTU3、ARP协议<1>什么是ARP协议<2>ARP数据报格式<3>ARP协议的工作机制4、其他重要协议或技术<1> DNS<2>NAT技术1、…

《MySQL实战45讲》——学习笔记19 “SQL查一行执行慢的排查、锁等待/一致性读“【建议收藏】

由于SQL本身的写法问题&#xff08;如join太多表、未走索引/索引失效、一次查太多数据等&#xff09;&#xff0c;或是MySQL节点CPU占用率很高或IO利用率很高&#xff0c;都会导致一条SQL执行的比较慢&#xff1b;但是有时候&#xff0c;"只查一行数据"&#xff0c;也…

内存优化之重新认识内存

我们知道&#xff0c;手机的内存是有限的&#xff0c;如果应用内存占用过大&#xff0c;轻则引起卡顿&#xff0c;重则导致应用崩溃或被系统强制杀掉&#xff0c;更严重的情况下会影响应用的留存率。因此&#xff0c;内存优化是性能优化中非常重要的一部分。但是&#xff0c;很…

深入体会线程状态的切换

✨✨hello&#xff0c;愿意点进来的小伙伴们&#xff0c;你们好呐&#xff01; &#x1f43b;&#x1f43b;系列专栏&#xff1a;【JavaEE初阶】 &#x1f432;&#x1f432;本篇内容&#xff1a;线程状态详解 &#x1f42f;&#x1f42f;作者简介:一名现大二的三非编程小白&am…

微机-------CPU与外设之间的数据传送方式

目录 一、无条件方式二、查询方式三、中断方式四、DMA方式一、无条件方式 外设要求:简单、数据变化缓慢。 外设被认为始终处于就绪状态。始终准备好数据或者始终准备好接收数据。 IN AL,数据端口 数据端口的地址通过CPU的地址总线送到地址译码器进行译码,同时该指令进行的是…

JAVASE(复习)——异常

所有的异常都是在java.lang包中的Throwable类中 一、Exception 和 Error 的区别 exception&#xff1a;程序本身发生的异常&#xff0c;可以捕获抛出异常&#xff0c;一般用try—catch—finally捕获。 error&#xff1a;发生在jvm层面的错误&#xff0c;程序无法处理。 二…

Git 如何调整 commit 的顺序

title: Git 如何调整 commit 的顺序 date: 2022-12-02 23:11 tags: [git] 〇、问题 使用哪条命令调整commit的顺序&#xff1f; git rebase -i 一、前言 今天测试了git hooks&#xff0c;产生了大量的commit&#xff0c;而后又进行了正常的commit&#xff0c;因此在这里是想要…

java——mybatis——Mybatis注解开发——@Update——修改数据

DAO接口&#xff1a; package com.sunxl.dao;import com.sunxl.pojo.User; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.SelectKey; import org.apache.ibatis.annotations.Update;impo…

SpringBoot+Thymeleaf上传头像并回显【表单提交】

参考文章&#xff1a;springbootthymeleaf实现图片上传并回显https://www.wanmait.com/note/shaowei/javaee/b3717a24fde24d3e89c47765a1a63214.html 一、新建SpringBoot项目 添加 spring web和 thymeleaf 的依赖 二、在templates新建页面 在页面中添加一个表单和一个文件上传…

8086,8088CPU管脚,奇偶地址体, ready信号,reset复位信号。规则字和非规则字

8086/8088均为40条引线&#xff0c;双列直插式封装&#xff0c;某些引线有多重功能&#xff0c;其功能转换有两种情况&#xff1a;一种是分时复用&#xff0c;一种是按组态定义。 用8088微处理器构成系统时&#xff0c;有两种不同的组态&#xff1a; 最小组态&#xff1a;808…

@AutoWired与@Resource

参考 : Qualifier - 搜索结果 - 知乎 Autowired和Resource的区别是什么&#xff1f; - 知乎 面试突击78&#xff1a;Autowired 和 Resource 有什么区别&#xff1f; - 掘金 目录 同一类型多个Bean报错问题 Resource注解 Resource的查找顺序 Resource注解实现依赖注入 Reso…

网课题库接口调用方法

网课题库接口调用方法 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点…

QT对象树机制

Qt提供了对象树机制&#xff0c;能够自动、有效的组织和管理继承自QObject的Qt对象。 每个继承自QObject类的对象通过它的对象链表&#xff08;QObjectList&#xff09;来管理子类对象&#xff0c;当用户创建一个子对象时&#xff0c;其对象链表相应更新子类对象信息&#xff0…

Docker快速入门

容器Docker技术的演进 1.曾经部署应用&#xff0c;使用物理机部署&#xff0c;这可能会因为不同应用所依赖的版本号不同&#xff0c;不得已购买一套全新的机器&#xff0c;所以成本高、部署慢、资源浪费、难以迁移和拓展、可能会被限定硬件厂商。 2.之后引入了VMVare&#xff…

使用JPA和Hibernate查询分页

介绍 受到我最近给出的StackOverflow答案的启发&#xff0c;我决定是时候写一篇关于使用JPA和Hibernate时查询分页的文章了。 在本文中&#xff0c;您将了解如何使用查询分页来限制 JDBC大小并避免获取不必要的数据。ResultSet 如何在#Hibernate中使用查询分页来限制 JDBC 结…

pytorch深度学习实战lesson32

第三十二课 分布式训练 这个是15年的时候沐神在 CMU 装的一个小机群&#xff0c;里面有30台机器&#xff0c;各机群有大概60块 GPU &#xff0c; 60块 GPU一共花了三四万美金的样子&#xff0c;就是大概20万人民币。沐神表示最亏的是当年他们跑了太多深度学习的实验&#xff0c…

C语言-const char*,char const*,char *const理解

By: Ailson Jack Date: 2022.12.04 个人博客&#xff1a;http://www.only2fire.com/ 本文在我博客的地址是&#xff1a;http://www.only2fire.com/archives/150.html&#xff0c;排版更好&#xff0c;便于学习&#xff0c;也可以去我博客逛逛&#xff0c;兴许有你想要的内容呢。…