Thread Run Method Execution Process


 

Thread Run Method Execution Process

Thread类的run()方法执行过程跟踪和思考

前言 : 之前听到很多说法都是:“单纯地执行Thread子类的run方法并没有多线程的效果,正确的做法是,用Thread子类对象执行Thread的start方法,这样start方法会自动执行子类的run方法。” 我对这种神奇的说法将信将疑,心里总感觉不踏实,于是点进Thread的源码了解了一下。

一. 入坑过程

然而,纵观 start 方法,并没有找到 run 这个词,那run方法又是在哪里被调用的呢?其中最有嫌疑的地方就是native方法start0

img

Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的。这个方法放在一个静态代码块中,当 Thread 类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法。 而本地方法 registerNatives 是定义在 openjdk\jdk\src\share\native\java\lang\Thread.c文件 中的。Thread.c 是个很小的文件,它定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如下:

img

可以容易的看出 Java 线程调用start->start0的方法,实际上会调用到 JVM_StartThread 方法,而 JVM_StartThread 方法则是在openjdk\hotspot\src\share\vm\prims\jvm.cpp中定义的

img

JVM_ENTRY 作为一个宏,用来定义 JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry,如下:

img

可见其中调用了 vmSymbols::run_method_name 方法, 而 run_method_name 是在 openjdk\hotspot\src\share\vm\classfile\vmSymbols.hpp 中用宏定义的:

img

这样不难想象到执行 Thread.start() 后的底层调用过程

img

二. 拓展

理解到 statrt0 方法为我们做了哪些事情后就很容易理解Java中线程启动的大致过程。

1. Thread子类

任务类继承Thread类,并覆盖它的run方法

img

subThread 调用父类的start方法时,由上面的过程可以知道,在JVM申请CPU线程资源并开启线程后,会执行 Thread类run 方法,而由于JVM多态支持,最终调用的是 SubThreadrun 方法,从而实现在一个线程中完成任务。

img

2. 实现Runnable接口

任务类实现 Runnable 接口,然后直接用该任务类对象来创建线程

img

在Thread类中有一个私有的Runnable类型的target属性,该属性在调用Thread(Runnable target)后将指向传入任务类对象

img

同样地,在调用完Threadstart方法后,将执行Threadrun方法,由于RunnableImplThread并没有继承关系,最终还是执行Thread.run()

img

可见通过在Thread中代理Runnable接口,可以处理任何线程任务,只要它们继承了Runnable接口,最终JVM都会通过Threadrun方法找到RunnableImplrun方法,将其中的任务加入线程中。

img

3. 考虑一次性线程

有些线程只需执行一次便被回收,这时可以使用匿名内部类的方式创建线程,以上的两者中方式可以换做

img

其执行过程与上面的基本类似,但是如果同时使用这两钟方式创建线程呢?到底谁的run方法会执行?

iimg

虽然Threadtarget属性指向了Runnable的匿名实现类对象,但是由于JVM的多态实现,最终并没有执行Threadrun方法,而是执行Thread的匿名子类中的run方法。

img