ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 导语 我们知道,new一个thread,调用它的start的方法,就可以创建一个线程,并且启动该线程,然后执行该线程需要执行的业务逻辑, 那么run方法是怎么被执行的呢? ## os线程 java的一个线程实际上是对应了操作系统的一个线程;而操作系统实现线程有三种方式: * 内核线程实现 * 用户线程实现 * 用户线程加轻量级进程混合实现 **内核线程实现** ![](https://box.kancloud.cn/0b84d41c885289c716c737cee98f8fc4_596x517.png) **简要说明**: 内核线程(Kernel-Level Thread简称KLT)是直接由操作系统内核直接支持的线程,这种线程由内核完成线程切换,内核通过操纵调度器对线程进行调度, 并负责将线程的任务映射到各个处理器上。 每个内核线程可以视为内核的一个分身,这样操作系统就有能力处理多件事情,这种支持多线程的内核就叫做多线程内核; 程序一般不会直接操作内核线程,而是去使用内核线程的一种高级接口:轻量级进程(Light Weight Process,简称LWP),轻量级进程就是我们通常所讲的 线程,每个轻量级进程都由一个内核线程支持,这种轻量级进程与内核线程1:1的关系称为一对一的线程模型。 **优势**: 由于有了内核线程的支持,每个轻量级进程都称为一个独立的调度单元,即使有一个轻量级进程在系统中阻塞了,也不会影响整个进程继续工作; **劣势**: 由于基于内核线程实现,所以各种线程操作(创建,析构及同步等)都需要进行系统调用(系统调用代价相对较高,需要在用户态`[User Mode]`和内核态`[Kernel Mode]`来回切换); 每个轻量级进程需要一个内核线程支持,因此需要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的; **用户线程实现** ![](https://box.kancloud.cn/f947e2cd55a614b08b3753df0606d1c9_594x330.png) **简要说明**: **用户线程**: * 广义上来讲,任何非内核线程都可以认为是用户线程(User Thread,简称UT); * 狭义上来讲,完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现; **特点**: * 用户线程的创建、同步、销毁和调度完全在用户态中完成,不需要内核的帮助,这种线程不需要切换到内核态,操作可以非常快速且低消耗; * 支持更大的线程数量; * 这种进程与用户线程之间1:N的关系称为一对多的线程模型; * 用户线程的优势就是不需要系统内核支援,劣势就是没有内核支持,所有的线程操作(如线程的创建、切换和调度等)都需要用户程序自己处理。 **用户线程加轻量级进程混合实现** ![](https://box.kancloud.cn/a26c040a94071b246b842f1964dff5d2_593x449.png) **简要说明**: **用户线程+轻量级进程特点**: * 用户线程的创建、析构、切换等操作很廉价; * 支持大规模的用户线程并发; * 操作系统提供的轻量级进程作为用户线程和内核线程之间的桥梁,提供线程调度功能及处理器映射; * 用户线程的系统调用通过轻量级进程完成,降低了整个进程完全被阻塞的风险; * 这种用户线程和轻量级进程的数量比不定,即N:M的关系,称为多对多的线程模型 ## Java线程 Java线程在JDK1.2之前,是基于用户线程实现的。而在JDK1.2中,线程模型替换为基于操作系统原生线程模型来实现。 而在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没法达成一致。 对于Sun JDK来说,它的Windows版本和Linux版本都是使用一对一的线程模型实现的,一条Java线程映射到一条轻量级进程之中。 ### Java线程创建 **创建方式** ~~~ public class TestThread { class ThreadExtend extends Thread { @Override public void run() { // TODO: 2018/9/6 } } class RunnableExtend implements Runnable { @Override public void run() { // TODO: 2018/9/6 } } private void testThreadPortal() { Thread thread1 = new ThreadExtend(); thread1.start(); Thread thread2 = new Thread(new RunnableExtend()); thread2.start(); } } ~~~ **Desc**:我们看到,无论以哪种方式创建,最终我们都会重写一个叫做 run 的方法,来处理我们的业务逻辑,然而我们都是调用一个start方法,来启动一个线程; 那 start方法和run方法之间是一个什么关系呢?从后边的介绍我们将获得这样一个信息:run就是一个回调函数,和我们普通的函数没有区别。 **Java线程的实现** 一个 Java 线程的创建本质上就对应了一个本地线程(native thread)的创建,两者是一一对应的。 关键问题是:本地线程执行的应该是本地代码,而 Java 线程提供的线程函数(run)是 Java 方法,编译出的是 Java 字节码, 所以, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。 以下是 Thread#start 方法的示例: ~~~ public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } ~~~ 可以看到它实际上调用了本地方法 start0, 而start0声明如下: `private native void start0();` 也就是新创建的线程启动调用native start0方法,而这些native方法的注册是在Thread对象初始化的时候完成的,look: ~~~ public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } //...... } ~~~ Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的。 这个方法放在一个 static 语句块中,当该类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法。 而本地方法 registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,它定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如下: ~~~ JNIEXPORT void JNICALL Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ //registerNatives (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); } static JNINativeMethod methods[] = { {"start0", "()V",(void *)&JVM_StartThread}, //start0 方法 {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, {"suspend0","()V",(void *)&JVM_SuspendThread}, {"resume0","()V",(void *)&JVM_ResumeThread}, {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, {"yield", "()V",(void *)&JVM_Yield}, {"sleep","(J)V",(void *)&JVM_Sleep}, {"currentThread","()" THD,(void *)&JVM_CurrentThread}, {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, {"interrupt0","()V",(void *)&JVM_Interrupt}, {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, }; ~~~ 观察上边一小段代码,可以容易的看出 Java 线程调用 start->start0 的方法,实际上会调用到 JVM_StartThread 方法,那这个方法又是怎么处理的呢? 实际上,我们需要看到的是该方法最终要调用 Java 线程的 run 方法,事实的确也是这样的。 在 jvm.cpp 中,有如下代码段: ~~~ JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)){ ... native_thread = new JavaThread(&thread_entry, sz); ... } ~~~ 这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry,如下: ~~~ static void thread_entry(JavaThread* thread, TRAPS) { HandleMark hm(THREAD); Handle obj(THREAD, thread->threadObj()); JavaValue result(T_VOID); JavaCalls::call_virtual(&result,obj, KlassHandle(THREAD,SystemDictionary::Thread_klass()), vmSymbolHandles::run_method_name(), //LOOK! 看这里 vmSymbolHandles::void_method_signature(),THREAD); } ~~~ 可以看到调用了 vmSymbolHandles::run_method_name 方法,而run_method_name是在 vmSymbols.hpp 用宏定义的: ~~~ class vmSymbolHandles: AllStatic { ... template(run_method_name,"run") //LOOK!!! 这里决定了调用的方法名称是 “run”! ... } ~~~ ## Java线程创建调用关系 ![](https://box.kancloud.cn/c61e2b56da97bf0cc8fd5168e468e1ab_604x352.png)