[TOC]
阅读约耗费10分钟…
转自原文: https://www.isgrow.cn/?p=15
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
再Linux系统中输入命令 man pthread_create
后如下图所示。
根据man配置的信息可以得出pthread_create会创建一个线程,这个函数是Linux系统的函数,可以用C或者C++直接调用,上面信息也告诉程序员这个函数在pthread.h
, 这个函数有四个参数
pthread_t *thread | 传出参数,调用之后会传出被创建线程的id | 定义 pthread_t pid; 继而 取地址 &pid |
---|---|---|
const pthread_attr_t *attr | 线程属性,关于线程属性是linux的知识 | 在学习pthread_create函数的时候一般穿NULL,保持默认属性 |
void *(*start_routine) (void *) | 线程的启动后的主体函数 相当于java当中的run | 需要你定义一个函数,然后传函数名即可 |
void *arg | 主体函数的参数 | 如果没有可以传NULL |
.c
的文件// 头文件
#include
#include
// 定义一个变量,接受创建线程后的线程id
pthread_t pid;
// 定义线程的主体函数
void* thread_entity(void* arg)
{
printf("I am new thread!\n");
}
// main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
int main()
{
while(1){
// 调用操作系统的函数创建线程,注意四个参数
pthread_create(&pid,NULL,thread_entity,NULL);
// usleep是睡眠的意思
usleep(100);
printf("I am main thread...\n");
}
}
gcc -o thread.out thread.c -pthread
thread.out
是thread.c
编译成功之后的文件
./thread.out
I am new thread!
I am main thread…
I am new thread!
I am main thread…
I am new thread!
I am main thread…
I am new thread!
…
一直交替执行…
假设有了上面知识的铺垫,那么可以试想一下Java的线程模型到底是什么情况呢?
/**
* @author Itachi [email protected]
* @Date 2020-05-30 12:38
*/
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I am thread for java");
}
});
t1.start();
}
}
这里启动的线程和上面通过Linux
的pthread_create
函数启动的线程有什么关系呢?
只能去查看start()
的源码了,看看java的start()
到底干了什么事才能对比出来.
可以看到这个方法最核心的就是调用了一个start0()
方法,而start0()
方法又是一个native
方法,故而如果要搞明白start0
我们需要查看Hotspot
的源码, 那我们就来看一下Hotspot
的源码, 一般直接看openjdk
的源码,我们先做一个大胆的猜测,java级别的线程其实就是操作系统级别的线程 也就是说:
我们调用start()
—> start0()
—> ptherad_create()
鉴于这个猜想来模拟实现一下!
/**
* @author Itachi [email protected]
* @Date 2020-05-30 12:38
*/
public class Demo {
private native void start0();
static {
System.loadLibrary("TestThreadNative");
}
public static void main(String[] args) {
Demo demo = new Demo();
demo.start0();
}
}
这里我们自己写的start0
调用一个本地方法,在本地方法里面去启动一个系统线程.
System.loadLibrary()
装载库,保证JVM在启动的时候就会装载。
然后我们写一个c程序来启动本地线程,文章开始时,写好了一段C程序,就用哪个吧。
// 头文件
#include
#include
// 定义一个变量,接受创建线程后的线程id
pthread_t pid;
// 定义线程的主体函数
void* thread_entity(void* arg)
{
printf("I am new thread!\n");
}
// main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
int main()
{
while(1){
// 调用操作系统的函数创建线程,注意四个参数
pthread_create(&pid,NULL,thread_entity,NULL);
// usleep是睡眠的意思
usleep(100);
printf("I am main thread...\n");
}
}
在Linux上编译并运行上述C程序
gcc thread.c -o thread.out -pthread
./thread.out
现在Java代码写好了,C程序也写好了。目前的问题就是我们如何通过start0
调用这个c程序?
这里就要用到JNI
了…(想起来了以前用Java调用易语言的DLL时候了…hhhh)
我们将写好的Java类上传至Linux系统中并编译!
在Linux下编译成clas文件: 编译: javac Demo.java 生成class文件:Demo.class 在生成 .h 头文件: 编译: javah Demo 生成h文件:Demo.h
在第15行代码 Java_Demo_start0
方法就是我们需要在C程序中定义的方法
然后继续修改.c程序,修改的时候参考.h文件,复制一份.c文件,取名threadNew.c
定义一个方法Java_Demo_start0
在方法中启动一个子线程,代码如下:
// 头文件
#include
#include
// 记得导入刚刚编译的那个.h文件
#include "Demo.h"
// 定义一个变量,接受创建线程后的线程id
pthread_t pid;
// 定义线程的主体函数
void* thread_entity(void* arg)
{
printf("I am new thread!\n");
}
// 这个方法要参考.h文件的15行代码
JNIEXPORT void JNICALL Java_Demo_start0(JNIEnv *env, jobject c1){
pthread_create(&pid,NULL,thread_entity,NULL);
while(1){
usleep(100);
printf("I am main thread\n");
}
}
// main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程
int main()
{
return 0;
}
把这个threadNew.c
编译成为一个动态链接库,这样在java代码里会被laod到内存libTestThreadNative这个命名需要注意libxx,xx就等于我们java那边写的字符串(System.loadLibrary("TestThreadNative");
)
解析类:
gcc ‐fPIC ‐I ${JAVA_HOME}/include ‐I ${JAVA_HOME}/include/linux ‐shared ‐o libTestThreadNative.so threadNew.c
如果执行上面的命令出现如下图的错误后,可尝试下面的这条命令…踩坑好久…原因未知…
gcc ./threadNew.c -I /opt/jdk1.8.0_251/include -I /opt/jdk1.8.0_251/include/linux -fPIC -shared -o libTestThreadNative.so
做完这一系列事情之后需要把这个.so文件加入到path,这样java才能load到
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:{libTestThreadNative.so}
直接测试,运行我们自己写的那个java类直接测试看看结果能不能启动线程
牛逼!我们已经通过自己写的一个类,启动了一个线程。
但是这个线程函数体是不是java的,是C程序的。
接下来我们来实现一下这个run!
…
…
…
未完待续…(踩上面那个坑踩了太久了,弄了大半天,心好累,不想写了。以后再说吧)