操作系统的线程
Linux操作系统启动一个线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
再Linux系统中输入命令 man pthread_create
后如下图所示。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201030101009250.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDU4NDE1Mg==,size_16,color_FFFFFF,t_70#pic_center)
根据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 |
- 在Linux上启动一个线程的代码,定义一个后缀名为
.c
的文件
// 头文件
#include <pthread.h>
#include <stdio.h>
// 定义一个变量,接受创建线程后的线程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");
}
}
- 执行命令编译 c 文件
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的线程模型到底是什么情况呢?
Java里的线程
在Java代码里启动一个线程
/**
* @author Itachi is_xianglei@163.com
* @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();
}
}
猜想Java中的线程就是调用操作系统的线程
这里启动的线程和上面通过Linux
的pthread_create
函数启动的线程有什么关系呢?
只能去查看start()
的源码了,看看java的start()
到底干了什么事才能对比出来.
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201030101034931.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDU4NDE1Mg==,size_16,color_FFFFFF,t_70#pic_center)
可以看到这个方法最核心的就是调用了一个start0()
方法,而start0()
方法又是一个native
方法,故而如果要搞明白start0
我们需要查看Hotspot
的源码, 那我们就来看一下Hotspot
的源码, 一般直接看openjdk
的源码,我们先做一个大胆的猜测,java级别的线程其实就是操作系统级别的线程 也就是说:
我们调用start()
—> start0()
—> ptherad_create()
鉴于这个猜想来模拟实现一下!
验证Java中的线程就是调用操作系统的线程
/**
* @author Itachi is_xianglei@163.com
* @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 <pthread.h>
#include <stdio.h>
// 定义一个变量,接受创建线程后的线程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
查看一下生成的.h文件
![image.png](https://img-blog.csdnimg.cn/img_convert/1c0d250b3602e581b1b86de17c3c7038.png)
在第15行代码 Java_Demo_start0
方法就是我们需要在C程序中定义的方法
然后继续修改.c程序,修改的时候参考.h文件,复制一份.c文件,取名threadNew.c
定义一个方法Java_Demo_start0
在方法中启动一个子线程,代码如下:
// 头文件
#include <pthread.h>
#include <stdio.h>
// 记得导入刚刚编译的那个.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
如果执行上面的命令出现如下图的错误后,可尝试下面的这条命令…踩坑好久…原因未知…
![](https://img-blog.csdnimg.cn/20201030101113342.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDU4NDE1Mg==,size_16,color_FFFFFF,t_70#pic_center)
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}
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201030101129347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDU4NDE1Mg==,size_16,color_FFFFFF,t_70#pic_center)
直接测试,运行我们自己写的那个java类直接测试看看结果能不能启动线程
执行命令 java Demo
![image.png](https://img-blog.csdnimg.cn/img_convert/34fec0626b190189f16cee47ef855939.png)
牛逼!我们已经通过自己写的一个类,启动了一个线程。
但是这个线程函数体是不是java的,是C程序的。
接下来我们来实现一下这个run!
模拟实现start方法是如何调用run方法的
…
…
…
未完待续…(踩上面那个坑踩了太久了,弄了大半天,心好累,不想写了。以后再说吧)