JVM源码剖析之线程的创建过程

说在前面:

对于Java线程的创建这个话题,似乎已经被"八股文"带偏~ 大部分Java程序员从"八股文"得知创建Java线程有N种方式,比如new Thread、new Runnable、Callable、线程池等等~ 而笔者写下这篇文章的目的是让大家从JVM源码的层面知道创建一个Java线程的方式。

版本信息:
jdk版本:jdk8u40

源码剖析:

public
class Thread implements Runnable {}

从Thread类的继承关系来看,Thread类实现Runnable接口,不少读者可能不明白为什么需要实现Runnable接口,也不明白Thread和Runnable之间的关系,那么笔者不妨以自己见解解释一番~

Thread:Java层面开发者使用的类,开发者可以使用它创建、启动、关闭线程等等操作

Runnable:一个函数式接口,无返回值、无入参,用于回调,其中编写开发者的回调逻辑(逻辑入口)

Callable:一个函数式接口,有返回值、无入参,用于回调,其中编写开发者的回调逻辑(逻辑入口)

所以Thread和Runnable之间的关系一目了然,一个是Java线程的API集合,一个是定义了逻辑入口的接口

为什么Thread需要实现Runnable,因为当线程启动后,Thread需要帮开发者自动执行线程执行体,所以需要一个执行入口方法,刚好Runnable接口就提供了此入口~

接下来看一下Thread的构造方法。

public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);}private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {if (security != null) {g = security.getThreadGroup();}if (g == null) {g = parent.getThreadGroup();}}g.checkAccess();if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target;setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);this.stackSize = stackSize;tid = nextThreadID();}

构造方法虽然比较长,但是都是对当前线程的属性赋值,并没有其他任何操作,所以笔者连注释都没有写。

写到这里,我们需要理解一个点,Java是没有能力创建底层的线程,这个需要交给JVM来创建,而这里new Thread创建的仅仅是Java层面表示的线程对象~

而JVM来创建线程,所以需要Java层面调用native方法,所以我们看到Thread中start方法。

public synchronized void start() {// 此线程的状态如果不是创建状态就直接抛出非法逻辑异常。if (threadStatus != 0)throw new IllegalThreadStateException();// 添加到对应的线程组中。group.add(this);boolean started = false;try {// Native方法,此方法会创建底层的线程,并且启动线程,并且执行线程的执行体(Runnable的run方法)start0();started = true;		// 正常启动完毕。} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {}}}private native void start0();

start方法会去底层创建线程,并且启动线程,执行线程的执行体 ,所以需要看到start0这个native方法的实现,所以下面是C/C++的代码~

{"start0",           "()V",        (void *)&JVM_StartThread}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);// 获取到Java开发者设置的栈大小,这个参数一般不会设置jlong size =java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));size_t sz = size > 0 ? (size_t) size : 0;// 创建底层的线程。// 当创建好线程后,会回调thread_entry此方法。native_thread = new JavaThread(&thread_entry, sz);if (native_thread->osthread() != NULL) {// 初始化线程,比如是否是守护线程,线程优先级。以及线程数量的统计。native_thread->prepare(jthread);}}// 设置成运行中状态Thread::start(native_thread);JVM_END

因为JVM是c/c++编写,而c++是有面向对象的思想存在,所以在new JavaThread中,会执行构造方法,我们看到构造方法。

JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :Thread()	// 调用父类构造方法
{// 初始化参数initialize();_jni_attach_state = _not_attaching_via_jni;// 设置执行入口,当底层线程创建好以后,会去回调此方法set_entry_point(entry_point);		// 设置此线程的类型,当前线程是java_thread类型os::ThreadType thr_type = os::java_thread;thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :os::java_thread;// 真正的线程只有操作系统才有权利去创建,所以这里调用操作系统类库去创建。os::create_thread(this, thr_type, stack_sz);_safepoint_visible = false;}bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {assert(thread->osthread() == NULL, "caller responsible");OSThread* osthread = new OSThread(NULL, NULL);osthread->set_thread_type(thr_type);osthread->set_state(ALLOCATED);thread->set_osthread(osthread);pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);…………	// 省略一部分设置参数代码// glibc guard pagepthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));ThreadState state;{pthread_t tid;// java_start 作为统一的入口,后续再根据thread类型做分发(多态)。int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);pthread_attr_destroy(&attr);osthread->set_pthread_id(tid);// Wait until child thread is either initialized or aborted{Monitor* sync_with_child = osthread->startThread_lock();MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);while ((state = osthread->get_state()) == ALLOCATED) {sync_with_child->wait(Mutex::_no_safepoint_check_flag);}}if (lock) {os::Linux::createThread_lock()->unlock();}}return true;
}

因为JVM是帮Java开发者实现了跨平台机制,所以需要适配所有OS平台,而我们只关心Linux操作系统,而Linux操作系统使用的是POSIX的线程标准,实现者肯定是Glibc类库,实现就是PThread线程库,而这里也是调用了pthread_create库函数创建了底层的线程(这里不懂没关系,只需要明白会去操作系统创建线程),并且设置线程的执行入口是java_start方法。

// pthread线程回调执行点。
static void *java_start(Thread *thread) {…………  // 省略大部分的设置参数和状态值的代码,方便观看核心源码// 这里是区分JVM层面抽象的不同线程的不同执行点(多态)// 而这里thread对象是JavaThreadthread->run();return 0;
}void JavaThread::run() {………… // 省略大部分的设置参数和状态值的代码,方便观看核心源码// 执行设置的入口thread_main_inner();
}

这里是pthread线程启动后回调的方法,这里会回调JavaThread的thread_main_inner方法。这里如果能看懂C++的读者会发现,这里使用多态思想非常完美~

void JavaThread::thread_main_inner() {// 不存在异常if (!this->has_pending_exception() &&!java_lang_Thread::is_stillborn(this->threadObj())) {HandleMark hm(this);// 回调设置的入口this->entry_point()(this, this);}DTRACE_THREAD_PROBE(stop, this);// 执行到这里代表线程执行结束了,需要做释放工作。this->exit(false);delete this;
}

 经过一系列的回调,最终来到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()),vmSymbols::run_method_name(),vmSymbols::void_method_signature(),THREAD);
}

这里非常的简单,在JVM层面调用Java的方法,而这里调用的就是Thread类中run方法(也即调用Runnable接口的run方法)

@Override
public void run() {// target参数往往是开发者传入的Runnable接口的实现类。if (target != null) {target.run();        // 调用开发者的逻辑}
}

上面的流程对于不懂JVM源码和不懂C++语言的读者来说非常吃力,甚至看不懂,这也很正常,源码层面是这样。但是读者会给你们做一个总结:

  1. 在Java层面调用Thread类的start方法
  2. start方法是一个native方法,会在c++层面创建JavaThread对象(因为c++也是面向对象)
  3. 在JavaThread的构造方法中会去创建OsThread对象(因为JVM是跨平台,存在很多个操作系统平台,所以需要一个OsThread做高度抽象)
  4. 在Linux操作系统平台中会去创建pthread线程(此线程可以理解为就是操作系统层面的线程)
  5. 创建完pthread线程后会经过一层一层的回调方法,最终回调thread_entry方法
  6. thread_entry方法中,会从JVM层面调用Java层面的方法,而调用的方法是Thread类中run方法,而Thread类是实现Runnable接口,所以也证明了文章开头说的Runnable 接口是作为一个执行入口
  7. 在Thread的run方法中,会去执行用户传入的Runnable接口,或者开发者重写了Thread的run方法。总之,这里就是回调开发者的逻辑。
  8. 所以Java的Thread创建入口就只有一个,就是Java的Thread类的start方法。

线程的关系图如上所述。

其实,你会发现,没有什么是中间抽一层不能解决的,每一层有每一层的职责,但是往往抽一层就需要有中间层的表示状态~

总结:

因为是深入到JVM源码层面,所以部分代码很多读者看不懂。但是代码是科学,他并不是神学,要论证真实性,必须要深入到源码层面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/132823.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maven 构建配置文件

目录 构建配置文件的类型 配置文件激活 配置文件激活实例 1、配置文件激活 2、通过Maven设置激活配置文件 3、通过环境变量激活配置文件 4、通过操作系统激活配置文件 5、通过文件的存在或者缺失激活配置文件 构建配置文件是一系列的配置项的值,可以用来设置…

【深度学习实验】卷积神经网络(七):实现深度残差神经网络ResNet

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. Residual(残差连接) __init__(初始化) forward(前向传播) 2. resnet_block(残…

Kotlin函数作为参数指向不同逻辑

Kotlin函数作为参数指向不同逻辑 fun sum(): (Int, Int) -> Int {return { a, b -> (a b) } }fun multiplication(): (Int, Int) -> Int {return { a, b -> (a * b) } }fun main(args: Array<String>) {var math: (Int, Int) -> Intmath sum()println(m…

Unity可视化Shader工具ASE介绍——6、通过例子说明ASE节点的连接方式

大家好&#xff0c;我是阿赵。继续介绍Unity可视化Shader编辑插件ASE的用法。上一篇已经介绍了很多ASE常用的节点。这一篇通过几个小例子&#xff0c;来看看这些节点是怎样连接使用的。   这篇的内容可能会比较长&#xff0c;最终是做了一个遮挡X光的效果&#xff0c;不过把这…

python随手小练5

1、求1-100的累加和&#xff08;终止条件 1-100&#xff09;&#xff08;while和for两种&#xff09; #while循环 count 0 index 0 while index < 100:count indexindex 1 print(count)#for循环 sum 0 for i in range(0,101):sum i print(sum)结果&#xff1a; 5050 2…

拓扑排序求最长路

P1807 最长路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目要求我们求出第1号到第n号节点之间最长的距离。 我们想到使用拓扑排序来求最长路。 正常来讲&#xff0c;我们应该把1号节点入队列&#xff0c;再出队列&#xff0c;把一号节点能到达的所有的点的入度减一&a…

oracle connect by详解

1、作用&#xff1a; 用于存在父子&#xff0c;祖孙&#xff0c;上下级等层级关系的数据表进行层级查询。 2、语法 SELECT ... FROM .... START WITH cond1 CONNECT BY cond2 WHERE cond3;2.1、说明 start with: 指定起始节点的条件 connect by: 指定父子行的条件关系 …

PyTorch 深度学习之加载数据集Dataset and DataLoader(七)

1. Revision: Manual data feed 全部Batch&#xff1a;计算速度&#xff0c;性能有问题 1 个 &#xff1a;跨越鞍点 mini-Batch:均衡速度与性能 2. Terminology: Epoch, Batch-Size, Iteration DataLoader: batch_size2, sheffleTrue 3. How to define your Dataset 两种处…

Verilog功能模块——同步FIFO

前言 FIFO功能模块分两篇文章&#xff0c;本篇为同步FIFO&#xff0c;另一篇为异步FIFO&#xff0c;传送门&#xff1a; Verilog功能模块——异步FIFO-CSDN博客 同步FIFO实现起来是异步FIFO的简化版&#xff0c;所以&#xff0c;本博文不再介绍FIFO实现原理&#xff0c;感兴趣…

Java面试题-0919

集合篇 Java面试题-集合篇HashMap底层实现原理概述javaSE进阶-哈希表 为了满足hashmap集合的不重复存储&#xff0c;为什么要重写hashcode和equals方法&#xff1f; 首先理解一下hashmap的插入元素的前提&#xff1a; hashmap会根据元素的hashcode取模进行比较&#xff0c;当…

【Java 进阶篇】创建 HTML 注册页面

在这篇博客中&#xff0c;我们将介绍如何创建一个简单的 HTML 注册页面。HTML&#xff08;Hypertext Markup Language&#xff09;是一种标记语言&#xff0c;用于构建网页的结构和内容。创建一个注册页面是网页开发的常见任务之一&#xff0c;它允许用户提供个人信息并注册成为…

Unity ToLua热更框架使用教程(1)

从本篇开始将为大家讲解ToLua在unity当中的使用教程。 Tolua的框架叫LuaFramework&#xff0c;首先附上下载链接&#xff1a; https://github.com/jarjin/LuaFramework_UGUI_V2 这个地址的是UGUI的。 下载完之后导入项目&#xff0c;首先&#xff0c;我们要先让这个项目跑起…