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 创建一个线程
从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
模拟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级别中的线程其实就是操作系统级别的线程
下面通过一个流程图总结:
补充说明(只做补充)
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
复制代码