Java 您所在的位置:网站首页 口唇紫绀好转怎么描述病情 Java

Java

2024-01-23 00:08| 来源: 网络整理| 查看: 265

【学习背景】

今天主要是来了解Java线程Thread中的run()、start()两个方法的执行有哪些区别,会给出一个简单的测试代码样例,快速理解两者的区别,再从源码层面去追溯start()底层是如何最终调用Thread#run()方法的,个人觉得这样的学习不论对面试,还是实际编程来说都是比较有帮助的。

进入正文~

学习目录 一、代码测试二、源码分析2.1 run()方法2.2 start()方法 三、使用总结

一、代码测试

执行Thread的run()、start()方法的测试代码如下:

public class MyThread { public static void print() { System.out.println("print..."); System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { System.out.println("执行print()方法的线程是:" + Thread.currentThread().getName()); print(); } }; System.out.println("一、调用run()方法的线程是:" + Thread.currentThread().getName()); thread1.run(); Thread.sleep(1000); System.out.println("二、调用start()方法的线程是:" + Thread.currentThread().getName()); thread1.start(); } }

测试结果:

一、调用run()方法的线程是:main 执行print()方法的线程是:main print... main 二、调用start()方法的线程是:main 执行print()方法的线程是:Thread-0 print... Thread-0

简单总结:

run()方法调用和执行重写的代码块都是由main主线程来完成的;start()方法调用是有main()主线程来调用,但是重写的代码块是由非main主线程来执行的。 二、源码分析 2.1 run()方法

通过线程调用run()方法,这个其实很简单,比如前面测试代码执行run方法:

thread1.run();

Ctrl + 鼠标左键,进入到java.lang.Thread的run()方法的源码:

@Override public void run() { if (target != null) { target.run(); } }

再Ctrl + Alt + 鼠标左键,定位前面测试类MyThread中重写的run()方法 在这里插入图片描述 可以发现,直接就调用到重写的run()方法了,并且,你可以重复调用多次,而且每一次都是由main主线程调用run()方法,然后非main主线程,执行run()方法代码块 在这里插入图片描述 虽然run()方法也可以直接执行到重写的代码块,不过实际当中,按规范来说,不会直接调用run()执行,而是通过start()方法来开启线程,start()方法底层会判断线程的状态,开启线程,最终调用run()方法并结束线程状态,往后了解。

2.2 start()方法

Oracle JDK源码stat()方法:

public class Thread implements Runnable { ... private ThreadGroup group; private volatile int threadStatus = 0; ... public synchronized void start() { //如果线程状态不是0-新建,则抛异常 if (threadStatus != 0) throw new IllegalThreadStateException(); //将当前线程添加到线程组 group.add(this); //线程是否开启,默认false-未开启 boolean started = false; try { //调用native本地方法(最终调用run()方法的核心入口) start0(); //执行完start0,说明线程已正常启动 started = true; } finally { try { if (!started) { //线程启动失败,则从线程组中将该线程删除 group.threadStartFailed(this); } } catch (Throwable ignore) { ... } } }

难点和重点是调用的start0()方法,这是一个native(本地方法/底层方法),源码:

//native本地方法,底层开启异步线程,由JVM创建并启动线程,并最终调用run()方法 private native void start0();

由于Oracle JDK对于native本地方法的源码隐藏了具体实现,可以去Open JDK官方或Github下载源码

以下载open jdk8最新的源码版本jdk8u60为例,需要下载jdk和hotspot两种源码来研究

下载jdk8u60源码 http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767 在这里插入图片描述下载jdk8u60的hotspot源码 http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/file/37240c1019fd 在这里插入图片描述 最后发现Open JDK官网下载实在是太慢了,而且还需要分别下载jdk8u60和jdk8u60_hotspot的源码 在这里插入图片描述

不过现在OpenJDK的源码已经放到GitHub,可以直接去GitHub下载,而且jdk源码和hotspot源码直接放一起了,省事,这里以下载jdk8-b60示例,下载地址:

https://github.com/openjdk/jdk/releases/tag/jdk8-b60

下载完解压,主要关注jdk和hotspot这两个源码目录: 在这里插入图片描述 使用相关工具,比如Scource Insight查看底层方法native stat0具体源码实现 (教程)安装Scource Insight (教程)使用Scource Insight打开OpenJDK源码 在这里插入图片描述

接下来通过Scource Insight查看底层方法start0是如何一步步,最终由主线程调用Thread#run()方法的源码分析过程

首先查看 源码文件:\jdk-jdk8-b60\jdk\src\share\native\java\lang\ Thread.c 关键源码:JVM_StartThread

static JNINativeMethod methods[] = { {"start0", "()V", (void *)&JVM_StartThread},//JVM开启线程的主入口 {"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}, {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName}, };

源码文件:\jdk-jdk8-b60\hotspot\src\share\vm\prims\ jvm.cpp 关键源码:new JavaThread

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) JVMWrapper("JVM_StartThread"); JavaThread *native_thread = NULL; bool throw_illegal_thread_state = false; { MutexLocker mu(Threads_lock); if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) { throw_illegal_thread_state = true; } else { jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread)); size_t sz = size > 0 ? (size_t) size : 0; native_thread = new JavaThread(&thread_entry, sz); if (native_thread->osthread() != NULL) { native_thread->prepare(jthread); } } } if (throw_illegal_thread_state) { THROW(vmSymbols::java_lang_IllegalThreadStateException()); } assert(native_thread != NULL, "Starting null thread?"); if (native_thread->osthread() == NULL) { delete native_thread; if (JvmtiExport::should_post_resource_exhausted()) { JvmtiExport::post_resource_exhausted( JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS, "unable to create new native thread"); } THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(), "unable to create new native thread"); } Thread::start(native_thread); JVM_END

源码文件:\jdk-jdk8-b60\hotspot\src\share\vm\prims\ jvm.cpp 关键源码:set_thread_status*RUNNABLE**

void Thread::start(Thread* thread) { trace("start", thread); if (!DisableStartThread) { if (thread->is_Java_thread()) { java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(), java_lang_Thread::RUNNABLE);//设置线程状态为RUNNABLE-运行状态 } os::start_thread(thread); } }

源码文件:\jdk-jdk8-b60\hotspot\src\share\vm\runtime\ os.cpp 关键源码:osthread->set_state(RUNNABLE)

void os::start_thread(Thread* thread) { // guard suspend/resume MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag); OSThread* osthread = thread->osthread(); osthread->set_state(RUNNABLE);//设置线程状态为RUNNABLE-运行状态, 子线程可以开始执行thread->run() pd_start_thread(thread); }

源码文件:\jdk-jdk8-b60\hotspot\src\os\linux\vm\ os_linux.cpp 关键源码:sync_with_child->notify()

void os::pd_start_thread(Thread* thread) { OSThread * osthread = thread->osthread(); assert(osthread->get_state() != INITIALIZED, "just checking"); Monitor* sync_with_child = osthread->startThread_lock(); MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag); //这里就是最终父线程调用thread->run()方法的主入口 sync_with_child->notify(); }

所以调用线程的start()方法,底层主要执行过程为: 在这里插入图片描述

三、使用总结

run()和start()方法的区别?

调用run()方法和执行重写run()方法的代码块都是由main主线程来完成的;调用start()方法是由main主线程来调用,但是重写的run()方法代码块是由非main主线程来执行的。除此之外,start()方法JDK底层Thread源码的start()方法中,会判断线程状态是否是新建状态,如果不是则抛异常IllegalThreadStateException-线程状态不合法异常并且后续会调用一个native start0()本地方法,本地方法是底层方法,Oracle JDK源码隐藏了native的具体实现,通过Open JDK的jdk源码和hotspot源码,查看native start0的底层源码,可以看到主要由c和c++实现的,start0会通过JVM_StartThread开启线程,并new JavaThread一个native_thread本地线程,然后设置线程状态为RUNNABLE运行状态,最后通过一个notify()方法实现主线程调用Thread#run()方法,程序结束后,线程状态会变成终止状态因此实际开发当中,应该规范的通过线程调用start()方法来开启线程。 ❤️‍原创不易,觉得有用的小伙伴点亮(点赞+收藏⭐),支持一下吧!❤️‍

在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有