序
在java编程中,Thread线程是我们经常使用的一个类。那么创建线程的本质是什么呢?本文就这一问题进行了探索。

内容主要分为以下几个部分
1.1的使用。JNI机制
2.线程创建线程的底层调用的分析
3.系统线程的使用
4.4中run方法的回调分析。线
5.实现jni回调
1.1的基本用途。JNI机制
当我们创建一个新的线程时,我们只是创建一个java级别的线程对象,只有当线程的start方法被调用时,一个线程才真正开始执行。所以启动方法是我们的重点。
检查线程类的start方法。
public synchronized void start { if抛出新的IllegalThreadStateExceptiongroup.addboolean started = false尝试{ start0开始=真;}最后{ try { if { group . thread start failed;}} catch {} }}Start方法本身并不复杂,它的核心是start0,真正启动线程。
让我们看看start0方法。
私有本机void start0可以看出这是一个原生方法。这里我们需要先解释一下什么是原生方法。
众所周知,java是一种跨平台的语言,用java编译的代码可以在任何安装了jvm的系统上运行。但是,每个系统的底层实现肯定是不同的。为了使java跨平台,jvm提供了一种称为java原生接口的机制。当java需要使用一些系统方法时,jvm会帮我们调用系统底层,java本身只需要告诉jvm需要做什么,也就是调用一个原生方法。
举个例子,当我们需要启动一个线程的时候,不管在哪个平台上,我们都调用start0方法。根据不同的操作系统,jvm调用相应的系统级方法来帮助我们真正的启动一个线程。所以这就好比jvm给我们提供了一个可以操作系统底层方法的接口,即JNI,java本地接口。
在深入研究start0方法之前,让我们实现我们自己的JNI方法,这样我们可以更好地理解start0方法如何在系统级调用本机方法。
让我们首先定义一个简单的java类。
包cn . tera . JNI;public class JniTest { public native void JNI hello;public static void main { JniTest JNI = new JniTest;jni.jniHello}}在这个类中,我们定义了一个jniHello的原生方法,然后在main方法中调用它。
然后我们调用javac命令将其编译成一个类文件,但是与往常不同的是,我们需要添加一个-h参数来生成一个头文件。
Java-h . JniTest.java注意-H后面有一个.号,表示生成的头文件存储在当前目录下。
此时,我们可以看到在当前目录下生成了两个新文件。
JNITEST . class:JNITEST类的字节码
中国TERA JNI JNITEST。h:。h头文件,在C和C++中需要,定义了方法的参数和返回类型,但不包含实现。它类似于java中的接口,java代码就是通过这个“接口”找到真正需要执行的方法。
我们看看。h文件,其中包含jniHello方法的定义。当然,应该注意的是,方法名和。h文件本身是由jni根据我们类的包名和类名决定的,不能修改。
# ifndef _ Included _ cn _ tera _ JNI _ JniTest # define _ Included _ cn _ tera _ JNI _ JniTest # ifdef _ _ cplusplusextern " C " { # endifjnie export void JNI call Java _ cn _ tera _ JNI _ JNI#ifdef __cplusplus}#endif#endif因为我们有。h头文件,我们自然需要。c或者。定义实际执行内容的cpp,也就是接口的实现。
我们希望这个方法简单地输出一个“hello jni”,所以我们定义了下面的方法,并将其保存在cn_tera_jni_JniTest.c文件中。
# include " cn _ tera _ JNI _ JniTest . h " jnie export void JNI call Java _ cn _ tera _ JNI _ JniTest _ JNI hello { printf;}在这个文件中。h文件,并定义了具有相同签名的Java _ CN _ TERA _ JNI _ JNITEST _ JNI holo方法。这时我们已经有了“接口”和“实现”,然后就可以生成动态链接库了。
Mac系统运行命令:
gcc-dynamic lib-I/library/Java/javavirtualmachines/JDK 1 . 8 . 0 _ 241.jdk/contents/home/includechn _特拉_ JNI _ jnitest . c-o libjnitest . JNI lib Linux系统运行命令:
gcc-shared-I/usr/lib/JDK 1 . 8 . 0 _ 241/include CHN _ tera _ JNI _ jnitest . c-o libjnitest . so-dynamic lb,-shared:表示我们需要生成一个动态链接库。
-I:以前,我们需要将jni.h引入。h头文件,这个文件位于jdk的目录中,其中-I表示包含。
-o:表示输出文件。
在Mac系统下,链接库的扩展名为jnilib,命名格式为libXXX.jnilib
在Linux系统下,链接库的扩展名是so,命名格式是libXXX.so
其中XXX是运行时加载动态库时使用的名称。
此时,目录中会多出一个libJniTest.jnilib或libJniTest.so的动态链接库。
最后,让我们回到最初的java文件,介绍一下这个库。修改JniTest.java
包cn . tera . JNI;class jnitest { static {//将查找路径设置为当前项目路径system . Set property;//加载动态库的名称System.loadLibrary。} public native void jniHellopublic static void main { JniTest JNI = new JniTest;jni.jniHello}}重新编译。类文件,记得把它放在。/cn/tera/jni目录,然后执行它。
Java.tera.jni.jnitest你好jni讲到这里,我们先来总结一下JNI的基本使用顺序。
1)在中定义本机方法。java文件。
2)生成相应的。h头文件。
3)写出相应的。c或者。cpp文件。
4)将接口和实现链接在一起,生成动态链接库。
5)在中介绍该库。java,你可以调用native方法。
2.线程创建线程的底层调用的分析
了解了jni的基本使用流程后,我们再回到Thread的start0方法。
探究start0方法的原理,自然要看jvm在幕后为我们做了什么。
首先,我们需要下载jdk和jvm的源代码。因为openjdk和oraclejdk差别不大,而且openjdk是开源的,所以我们参考openjdk的代码,版本是jdk8。
下载地址:http://hg.openjdk.java.net/jdk8
因为C和C++的代码对于java程序员来说比较晦涩,所以我在下面展示源代码的时候只贴上我们关心的关键代码,其余的就省略了。
在jdk源代码的src/Java . base/share/native/lib Java目录下可以看到Thread.c文件,对应的是jni中的“实现”。
# include " JNI . h " # include " JVM . h " # include " Java _ lang _ thread . h "...静态JNINativeMethod方法[] = { {"start0 "," V ",JVM_StartThread},...};JNI export void JNICALLJava _ Java _ lang _ Thread _ register natives {-> register natives);}根据之前自己定义的jni实现,这个文件中应该有一个Java_java_lang_Thread_start0的方法定义,但实际上只有一个Java _ Java _ lang _ Thread _ registerNatives的方法定义,对应的是Thread.java中的registerNatives方法:

类线程实现Runnable { private static native void register nations;静态{ registerNatives} ...}由此我们可以发现,Thread类在实现jni时,并不是直接在自己的头文件中定义每一个native方法,而是通过一个registerNatives方法动态注册。注册需要的信息在methods数组中定义,包括方法名、方法签名和接口方法,接口方法的定义统一在jvm.h中,此时jni接口方法的名称不再受固定格式的限制。这种机制将在后面的单独文章中解释。现在,我们应该首先关心线程的本质。
接下来,我将在调用链中从上到下列出文件和方法。
1)jvm.h,热点目录src/share/vm/prims
由于start0方法的接口方法是在jvm.h中定义的,所以我们可以先看jvm.h找到JVM_StartThread的定义:
JNI export void JNICALLJVM _ start thread;2)jvm.cpp,热点目录src/share/vm/prims
然后我们再来看jvm.cpp,在这里可以看到JVM_StartThread的具体实现。关键是通过创建一个JavaThread类来创建一个线程。注意,JavaThread在这里是一个C++级别的线程:
JVM _ ENTRY)JVM wrapper;JavaThread * native _ thread = NULLbool throw _ illegal _ thread _ state = false;{...native _ thread = new JavaThread;...} ...JVM_END3)thread.cpp,热点目录src/share/vm/runtime
查看thread.cpp,您可以看到JavaThread的构造函数,其中创建了一个系统线程:
Java线程::Java线程:线程{...OS::Create _ thread;}4)os_linux.cpp,热点目录src/os/linux/vm
我们可以在hotspot源码目录的src/os下找到不同系统的方法。我们以linux系统为例。
查看os_linux.cpp,找到create_thread方法:
bool os::create_thread {...pthread _ t tidint ret = pthread _ create)thread _ native _ entry,thread);...}这个pthread_create方法是最终创建系统线程的底层方法。
所以java线程启动方法的本质其实就是通过jni机制调用系统底层的pthread_create方法来创建一个系统线程,所以java线程和系统线程是一一对应的关系。
3.系统线程的使用
我们简单使用一下这种线程创建方法。创建以下内容。c文件,在main方法中创建一个线程,让两个线程连续打印一些副本。
# include # include pthread _ t pidvoid * thread _ entity { while { printf;} } int main { pthread _ createwhile { printf}返回1;}编译文件。
gcc thread demo . c-o threaddemo.out-o:编译后的可执行文件为thread demo . out。
运行out文件后,可以看到重复打印了两份,即通过pthread_create方法成功创建了一个系统级线程。
4.4中run方法的回调分析。线
至此,我们的探索还没有结束。在java的Thread类中,我们会传入一个执行我们指定任务的runnable对象,并在Thread的Run方法中调用它。java通过jni调用pthread_create创建系统线程时,如何回调java中的run方法?
在之前的探索中,我们从java层开始,从上到下进行搜索。这时候我们就要从下往上逆向搜索了。
1)pthread_create
让我们看看pthread_create方法本身。它接收四个参数,其中第三个参数start_routine是创建系统线程后要执行的方法,就像我们前面创建的简单例子中的thread_entity一样,第四个参数arg是start_routine方法需要的参数。
pthread_create,void * arg);2)os_linux.cpp
查看create_thread方法中调用pthread_create的代码,可以看到thread_native_entry是系统线程执行的方法,thread是传递给thread_native_entry的参数:
int ret = pthread _ create)thread _ native _ entry,thread);看看Thread_native_entry方法。它得到的参数只是一个线程,调用它的run方法。注意,这个线程是C++级别的线程,来自pthread_create方法的第4个参数:
静态void *thread_native_entry {...//多调用一级启动例程线程->运行;...返回0;}3)线程. cpp
检查JavaThread::run方法,其主要执行内容在thread_main_inner方法中:
Void Java Thread:: run {Thread _ Main _ inner;}检查JavaThread::thread_main_inner方法,其内部回调是通过entry_point进行的:
void javathread::Thread _ Main _ Inner {...this-> entry _ point;...}看JavaThread::JavaThread构造函数,可以看到这里的entry_point是从外部传入的。
JavaThread::JavaThread : Thread{...设置_入口_点;...}4)jvm.cpp
查看JVM_StartThread方法,可以看到传递给JavaThread的entry_point是thread_entry。
JVM _ ENTRY)JVM wrapper;JavaThread * native _ thread = NULLbool throw _ illegal _ thread _ state = false;{...native _ thread = new javathread;...} ...JVM_END看thread_entry,其中调用javaCalls::call_virtual回调Java级方法。其实看它的方法签名就能猜到。
静态void thread _ entry { HandleMark hmHandle obj); JavaValue结果; JavaCalls::call_virtual),//方法名vmSymbols::run_method_name,//方法签名VM symbols::void _ method _ signature,thread);}5)vmSymbols.hpp,热点目录src/share/vm/classfiles
当我们看得到方法名run_method_name和方法签名void_method_signature的部分,可以看到,是方法得到了方法名run,没有得到任何参数,返回值是void:
模板...templateV”)那么系统线程就可以成功回调java级的run方法了!
在这里,我整理了一下Thread的start0方法的上下游关系,以便大家从整体上把握。
Thread.java
- >jvm.cpp
- >thread.cpp
- >os_linux.cpp
- >pthread_create
5.实现jni回调
最后,我们尝试自己实现一个简单的方法回调。
修改初始JniTest.java并添加一个回调方法:
包cn . tera . JNI;class jnitest { static {//将查找路径设置为当前项目路径system . Set property;//加载动态库的名称System.loadLibrary。} public native void jniHello//添加一个新的回调方法,public void callback { system . out . println;} public static void main { JniTest JNI = new JniTest;jni.jniHello}}修改cn_tera_jni_JniTest.c文件。本来只是一个副本的简单输出,现在被回调为java方法。你可以看到这个过程非常类似于java中的反射机制:
# include " cn _ tera _ JNI _ jnitest . h " JNI导出void JNI调用Java _ cn _ tera _ JNI _ jnitest _ JNI hello {//获取类信息jclassthisclass =-> getobject class;//根据方法名和签名,得到方法的id jmethodic mid callback =--> getmethodiv”);//调用方法-> callvoid method;}重新生成动态链接库,编译。类文件,并运行:
gcc-dynamic lib-I/library/Java/javavirtualmachines/JDK 1 . 8 . 0 _ 241.jdk/contents/home/includechn _ tera _ JNI _ jnitest . c-o libjnitest . jnilibjavac jnitest . javajavacn . tera . JNI
这个回调,当然jni也为带参数的回调和返回的结果提供了不同的调用方法,这里就不展开了。有兴趣的同学可以自己看看jni.h文件。
还需要提到的是,上面展示的回调只是最基本的使用,jvm中官方的回调方法,因为涉及到java的父类继承、方法句柄、vtable等等,这里就不展开了。学生应该自己研究。

最后,总结本文的内容。
1.实现一个jni只需要四样东西。java文件,。h头文件。c或者。cpp文件,以及生成的动态链接库。
2.java的线程最终通过jni机制调用到系统底层的pthread_create方法来创建线程。
3.线程的jni调用链:thread . Java-> JVM . CPP-> thread . CPP-> OS _ Linux . CPP-> pthread _ create
4.jni还可以回调java方法,完成一个从调用到回调的演示。
原文链接:file/tupian/20220929/13937611.html


