如图3-18所示,在Java层面,其实线程只能算是一个任务,这个任务需要单独启动一个线程去执行。JVM接收到线程任务以后会向操作系统请求创建一个内核线程,操作系统接收到请求以后会在内核中创建一个线程返回给JVM,内核线程需要执行Thread类的run方法。
图3-18 Java线程映射
JVM接收到线程创建成功消息之后会等待操作系统调度、激活线程执行任务。线程创建的详细流程如图3-19所示。
图3-19 Java线程创建与执行过程
通常,Java中的线程任务首先要实现Runnable接口。并通过Runnable的实例来构造Thread对象,并调用Thread的start方法来启动线程。Thread的start方法最终会调用本地方法start0来启动线程。start0方法的对应JNI函数是JVM_StartThread。
收到线程创建请求后,JVM_StartThread函数会创建一个JavaThread对象。JavaThread构造函数会调用os::create_thread函数来创建系统内核线程。os::create_thread函数在不同的操作系统上有不同的实现,在UNIX、Linux、Mac OS操作系统上都是用pthread_create函数来创建线程。
创建线程后需要有线程的执行函数:thread_native_entry。thread_native_entry会调用Thread::call_run函数,最终会调用Thread::run方法,即Runnable接口中的run方法。
接下来用Java线程的例子为起点,一层层深入分析线程创建的核心链路。BasicTask是一个简单的线程示例,如代码清单3-15所示。BasicTask实现了Runnable接口,它的run方法实现了简单的两个整数相加计算,并在main函数里启动了线程来执行run方法。
代码清单3-15 BasicTask线程示例
Thread类的构造函数的核心是调用了init方法,如代码清单3-16所示。init方法主要是设置了线程名称、线程的父线程、线程组、线程栈大小等信息。
代码清单3-16 Thread类init方法
nextThreadID方法是用来生成一个线程ID,线程的ID是全局自增的long值,每次调用都会创建一个线程ID。
通常在创建Thread对象后,需要调用start方法来正式启动一个线程,如代码清单3-17所示。start方法会判断当前线程是否已经启动过了,如果是重复启动则会直接抛出异常。
代码清单3-17 start方法
如果线程以前没有启动过,会调用start0方法来启动线程,如代码清单3-18所示。
代码清单3-18 调用start0本地方法
JVM的Thread.c里存储了Thread类的所有本地方法对应的JNI处理函数,如代码清单3-19所示。start0方法对应的JNI函数是JVM_StartThread,stop0方法对应的JNI函数是JVM_StopThread,isAlive方法对应的JNI函数是JVM_IsThreadAlive。
代码清单3-19 Thread类对应的JNI处理函数
JVM_StartThread函数负责线程的创建与启动,首先会设置线程栈的大小,然后构建一个JavaThread对象。代码清单3-20是JVM_StartThread函数的源代码。
代码清单3-20 JVM_StartThread函数
JVM_StartThread核心流程如下:首先获取MutexLocker全局互斥锁,以确保同一时刻只能创建一个线程,确保线程创建的安全性。接着判断当前线程是否已经启动,如果已经启动了就报错,一个线程不能重复创建。接着设置线程栈的大小,一般都是采用JDK默认的-Xss参数来设置的。然后创建一个JavaThread对象,JavaThread对象的构造函数会创建系统内核线程。如果线程创建失败就进行系统报错。如果创建成功了,则调用Thread类的start函数来启动一个线程。
JavaThread构造函数的主要功能是调用os::create_thread来完成内核线程的创建,如代码清单3-21所示。
代码清单3-21 JavaThread对象的构造函数
os::create_thread在Linux操作系统上是通过pthread_create来创建的,如代码清单3-22所示。os::create_thread函数首先会创建一个OSThread(系统线程)对象,并设置好线程类型、状态、线程栈大小等信息,最后把JavaThread对象与OSThread进行关联。设置pthread_create需要的线程属性pthread_attr_t,主要是设置初始状态、线程栈大小等信息。调用pthread_create函数在操作系统创建核心线程,如果失败,最多重试创建3次。如果最终创建失败就直接抛出错误。如果创建成功,则把线程ID赋值给OSThread。然后等待线程的对象变为ALLOCATED初始化的状态,说明线程初始化结束。
代码清单3-22 Linux系统os::create_thread函数
通过代码清单3-22可以发现,在创建内核线程的时候传入的函数是thread_native_entry。在内核线程创建完成后,系统会自动调用thread_native_entry函数来执行线程任务。代码清单3-23是JVM线程执行入口函数thread_native_entry的具体实现。
代码清单3-23 JVM线程执行入口函数
thread_native_entry函数的执行流程如下:首先设置线程的栈地址信息、初始化线程的当前状态、线程的通信掩码与线程的浮点寄存器信息,接着设置线程的状态为INITIALIZED的状态,最后调用Thread的call_run函数来执行线程任务。
call_run函数功能非常简单,设置好线程栈结束的地址,调用pre_run方法来实现线程执行前的预处理,然后调用run方法来执行任务,最后调用post_run来做线程执行完成的善后处理工作。这个就相当于我们熟悉的动态代理功能,在线程执行前、后各增加一个处理器。pre_run函数是空实现,没有任何意义。post_run函数主要用来释放线程相关的资源信息。代码清单3-24是call_run函数的具体实现。
代码清单3-24 Thread的call_run函数
JavaThread的run函数主要负责线程任务的执行,具体实现如代码清单3-25所示。首先初始化全局的线程变量,然后创建线程的保护页,设置线程的状态为_thread_in_vm,接着刷新缓存,最后调用thread_main_inner函数执行任务。
代码清单3-25 JavaThread的run函数
3.3节提到,在子线程启动前,父线程对数据的修改要对子线程可见。这个功能就是通过调用OrderAccess的cross_modify_fence方法来实现的。cross_modify_fence函数会刷新CPU本地缓存,并通知其他CPU数据已经更改,从而线程能够读取到父线程修改的数据。
thread_main_inner函数主要申请线程栈资源,调用entry_point函数来执行具体的任务,实现如代码清单3-26所示。entry_point是个ThreadFunction线程函数对象,是Java里Thread类的run方法编译后的函数。
代码清单3-26 JavaThread的thread_main_inner函数