java的本质_聊一聊Java 线程的本质

Java是如何启动线程的呢?当Java调用了Thread.start()方法做了些什么?Java中的线程和操作系统中的线程是什么关系呢?

Linux 开启线程

首先,我们先看一下Linux是如何开启一个线程的,这里涉及的知识是很复杂的,我只说一下大概的流程。

如下面的代码,是Linux的底层的源码,主要通过pthread_create() 方法是glibc库提供的,该方法的作用就是去创建一个线程。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

void *(*start_routine) (void *), void *arg);

复制代码

如果你有linxu 系统可以输入:man 3 pthread_create 来查询这个方法的作用,如下图所示:描述 create a new thread 创建一个线程

a31bb64a5d02a0aa45470c8d8f1b3711.png

从Linux的帮助文档中,可以看出pthread_create会创建一个线程,这是Linux系统的函数,可以用C或者C++直接调用,下面看一下这个方法中的四个参数的意义

参数名字参数定义参数解释pthread_t  *thread传出参数,调用之后会传出被创建线程的id定义 pthread_t pid 继而取地址 &pid

const pthread_attr_t  *attr线程属性,关于线程属性是Linux的知识一般传null,保持默认属性

void (start_routine) (void *)线程的启动后的主体函数需要你定义一个函数,然后传函数名

void *arg主体函数的参数没有可以传null

通过上面的解释,可以得出Linux启动线程的一个测试代码

//头文件

#include

#include

//定义一个变量,接受创建线程后的线程id pthread_t pid;

//定义线程的主体函数

void* thread_entity(void* arg){

printf("i am new Thread! from c");

}

//main方法,程序入口,main和java的main一样会产生一个进程,继而产生一个main线程

int main(){

//调用操作系统的函数创建线程,注意四个参数

pthread_create(&pid,NULL,thread_entity,NULL);

//usleep是睡眠的意思,那么这里的睡眠是让谁睡眠呢?

//为什么需要睡眠?如果不睡眠会出现什么情况

usleep(100);

printf("main\n");

return 0;

}

复制代码

Java 启动线程

java启动线程的代码如下:

Thread thread = new Thread(new Runnable() {

@Override

public void run(){

System.out.println("java thread");

}

});

//调用start方法 --> run方法

//private native void start0();

thread.start();

复制代码

进入Thread的源码,可以找到真正开启线程的是:private native void start0(); 方法,start0方法会调用JVM的底层方法也就是Hotspot的源码,而JVM的源码对调用系统OS层的xxx.c文件中的pthread_create 方法创建线程。

为了进一步证明,我们可以模拟一下Java代码通过JNI直接调用.c代码,然后.c实现调用Linux函数pthread_create 创建一个线程。

#include

#include //定义变量接受线程id

pthread_t pid;

//线程的主体方法相当于 java当中的run 这里我们不去探究JVM是如何调用Java中的run方法的,因为涉及的都是C/C++层面的 我们只需要知道,Linux创建的线程会执行到Java中的run方法就可以了

void* thread_entity(void* arg){

//子线程死循环

while(1){

//睡眠100毫秒

usleep(100);

//打印

printf("I am new Thread\n");

}

}

//c语言的主方法入口方法,相当于java的main

int main(){

//调用linux的系统的函数创建一个线程

pthread_create(&pid,NULL,thread_entity,NULL); //主线程死循环

while(1){

//睡眠100毫秒

usleep(100);

//打印

printf("I am main\n");

}

return 0;

}

复制代码

下面我们来测试一下,上述的c程序,是否可以在Linux中运行,执行命令:

gcc thread.c - thread.out -pthread

运行程序:./thread.out

f4e1a95cc0b5c7081d0bd1aacb32fff1.png

模拟Java的线程,调用start1本地方法,在本地方法里面去启动一个系统线程。

public class ZLThread{

static {

System.loadLibrary("EnjoyThreadNative");

}

public native void start1();

public static void main(String[] args){

ZLThread zlThread = new ZLThread();

zlThread.start1();

}

}

复制代码

在Linux系统上面,利用javac ZLThread.java命令编译这个Java类,利用java -h xx.java命令 来编译一个头文件,然后找到头文件中的方法,复制给上述的.c文件,我们需要Java来调用c程序,就需要将方法设置正确,并且编译为so文件

#include

#include //定义变量接受线程id

pthread_t pid; //线程的主体方法相当于 java当中的run

void* thread_entity(void* arg){

//子线程死循环

while(1){

//睡眠100毫秒

usleep(100);

//打印

printf("I am new Thread\n");

}

}

//这个方法名字需要参考.h当中的方法名字,打开.h文件,复制方法名过来

//参数固定

Java_com_shadow_app_EnjoyThread_start0(JNIEnv *env, jobject c1) {

//调用linux的系统的函数创建一个线程

pthread_create(&pid,NULL,thread_entity,NULL); //主线程死循环

while(1){

//睡眠100毫秒

usleep(100);

//打印

printf("I am main\n");

}

}

复制代码

编译so文件的命令:

gcc -fPIC -I /home/shadows/soft/jdk11/include -I/home/shadows/soft/jdk11/include/linux -shared -o libxxx.so threadNew.c

注意生成的so文件名称要和Java程序中的相对应

static { //如果你是libxxx;这里就写xxx

System.loadLibrary( "xxx" );

}

复制代码

然后把so文件目录添加到系统变量,否则Java是找不到so文件的

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/shadows/tools/enjoy/com/shadow/app/

然后通过Java命令去运行Java文件,可以看到Java创建了线程。

这也就佐证了,Java调用Thread.start()方法其实就是jvm调用OS层的底层方法去创建线程。这也是为什么调用了start 方法,Java的线程状态会进入就绪状态,不会立即启动,因为通过OS,CPU 创建线程,创建线程完毕之后通过JNI调用Java的run方法。

OK,至此也就证明了文章开头的三个问题:

Java是通过JVM调用OS系统创建线程

Thread.start()经历了:Thread.start() --> navtive start0() --> JVM ---> Thread.c : start0() --> JVM 实例化一个C++对象 JavaThread --> OS pthread_create() 创建线程 --> 线程创建完毕 --> JVM --> Thread.run()方法

Java级别中的线程其实就是操作系统级别的线程

下面通过一个流程图总结:

5ce26b574e6f928dfba41905c5ea90e7.png

补充说明(只做补充)

man命令 是Linux下的帮助指令,通过man指令可以查看Linux中的指令帮助、配置文件帮助和编程帮助等信息。

语法

man(选项)(参数)

复制代码

选项

-a:在所有的man帮助手册中搜索;

-f:等价于whatis指令,显示给定关键字的简短描述信息;

-P:指定内容时使用分页程序;

-M:指定man手册搜索的路径。

复制代码

参数

数字:指定从哪本man手册中搜索帮助;

关键字:指定要搜索帮助的关键字。

数字代表内容

1:用户在shell环境可操作的命令或执行文件;

2:系统内核可调用的函数与工具等

3:一些常用的函数(function)与函数库(library),大部分为C的函数库(libc)

4:设备文件说明,通常在/dev下的文件

5:配置文件或某些文件格式

6:游戏(games)

7:惯例与协议等,如Linux文件系统,网络协议,ASCII code等说明

8:系统管理员可用的管理命令

9:跟kernel有关的文件

复制代码

实例

我们输入man ls,它会在最左上角显示“LS(1)”,在这里,“LS”表示手册名称,而“(1)”表示该手册位于第一节章,同样,我们输man ifconfig它会在最左上角显示“IFCONFIG(8)”。也可以这样输入命令:“man [章节号] 手册名称”。

man是按照手册的章节号的顺序进行搜索的,比如:

man sleep

复制代码

只会显示sleep命令的手册,如果想查看库函数sleep,就要输入:

man 3 sleep

复制代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值