【Hotspot源码】揭秘Java线程创建过程中的各种细节

近期准备给大家分享专题系列文章,聚焦Java多线程机制。会从hotspot源码角度,给大家揭秘平时学习多线程那些从来没有想过的问题,或者存在疑虑却又无法证明的理论。

今天是系列文章首篇,咱们来谈谈Java线程创建的一些细节问题:

  1. Java线程是如何与OS线程建立联系的

  2. Java线程与OS线程共用一套线程状态吗

  3. Java线程是如何做到创建与启动分开的

  4. Java线程在JVM层面为什么要有JavaThread与OSThread

  5. Java线程为什么设计的时候要将创建与启动分开

先把这五个问题搞明白吧,其他的后面的文章再分享。本篇文章的观点聚焦于Linux平台,不适用于所有平台。因为不同OS,底层差异还是挺大的。

Linux线程创建

线程能力是操作系统才有的,固Java的线程机制一定是基于OS的线程机制实现的,加上些许JVM自身的考虑在其中。这些考虑在哪能看到?JSR规范中。

上一段Linux平台下创建线程的代码

图片

这样创建出来的线程,一般称为原生线程,或native thread。Java的线程实现其实就是将Linux下的线程机制基于JSR规范进行设计重组。如果我们了解Linux的线程机制,并搞明白了JVM是如何进行设计重组的。那么,Java的多线程,我们就算真正学明白了。

可以发现,跟Java创建线程明显不同的是:原生线程创建与运行是一体的,即线程创建完毕马上就运行。而Java中创建归创建,调用start线程才运行。

Java线程与原生线程之间是这样的关系:Thread对象->JavaThread->OSThread->native thread。下文会结合hotspot源码细讲。

Java线程创建

上一段创建Java线程的代码

图片

从研究问题的角度,这段我们习以为常的代码要分成两部分来看:一、创建一个Java对象。注意,这一步只是单纯地创建一个Java对象,并没有什么特殊处理在里面。二、调用start方法让线程运行。我们上面提的几个问题,所有的秘密都在这一步中。接下来我结合hotspot源码把我对上面几个问题的思考分享给大家。

第一个问题:Java线程是如何与OS线程建立联系的,先上代码,这里只保留了关键代码

图片

分析下这段代码:

  1. 第2句创建了一个OSThread对象。第4句将JavaThread与OSThread建立联系。

  2. 第16句是创建原生线程。这一步执行完,新创建的线程就会马上执行java_start方法,java_start方法最终会通过JNI调用Java代码中的run方法。

  3. 第18句是将原生线程的ID存储到OSThread中。因为Linux下所有操作线程的API都需要传入线程ID。

  4. 总结来说,Java线程与原生线程之间是这样的关系:Thread对象->JavaThread->OSThread->native thread。

第二个问题:Java线程与OS线程共用一套线程状态吗?不是。JavaThread中有线程状态,OSThread也有线程状态。这个问题后面写篇文章细讲。

图片

第三个问题:Java线程是如何做到创建与启动分开的?答案是借助锁。研究过Hotspot源码的小伙伴可能知道,这里的锁要么是parker对象, 要么是ParkEvent。这两个锁对象是理解多线程不可逾越的横沟,后面写篇文章细讲。

图片

如果你对Java线程创建的细节能掌握到这个程度,理论上来说面试基本不会被虐。我再给你一些面试装叉的灵丹妙药。

分离线程

大家有没有注意到os::create_thread中的第8句代码。这句代码是设置即将创建的线程属性,值为PTHREAD_CREATE_DETACHED。这个值的意思是以分离状态创建线程。即我们通过new Thread创建的线程于OS而言都是分离线程。

为什么要创建分离线程,而不是普通线程呢?这就要说到分离线程的好处。也不得不感叹写JVM的大佬对硬件、对OS的精通程度。

在我们使用默认属性创建一个线程的时候,线程是 joinable 的。joinable 状态的线程,必须在另一个线程中使用 pthread_join() 等待其结束, 如果一个 joinable 的线程在结束后,没有使用 pthread_join() 进行操作, 这个线程就会变成"僵尸线程"。每个僵尸线程都会消耗一些系统资源, 当有太多的僵尸线程的时候,可能会导致创建线程失败,因为每个进程能够持有的描述符是有限的。

当线程被设置为分离状态后,线程结束时,它的资源会被系统自动的回收, 而不再需要在其它线程中对其进行 pthread_join() 操作。

Linux系统层面的线程知识,我在我的手写JVM二期班中都会给大家补上。

我的困惑

Java的线程机制我觉得设计的过于复杂了,两个方面:一、需要维护两套线程状态;二、创建与启动分开。

目前不太理解JVM为什么要这样设计,所以我在手写JVM的课程中,我准备反其道而行,看看这样实现存在什么样的问题或者不便,这些问题或不便应该就是JVM这样设计的理由。这也就是我开设手写JVM小班的意义之一,你看到的所有理论,你心中的所有猜疑,如果你有一个自己手写自己熟悉的JVM,你就可以随时去论证,找到正确答案。而不是模棱两可、自己都没有底气的猜疑。

有些小伙伴可能想,我直接改hotspot源码不也可以吗?的确,可以,但是这个门槛太高了。你如果没有能力手写一个JVM,改hotspot源码那就是天方夜谭了。

结语

总是听到小伙伴说:多线程好难学、多线程学不会、多线程没概念……为什么会这样呢?我觉得可能是这样:大多数小伙伴接触线程都是从高级语言开始的,比如Java。而高级语言的线程是基于操作系统的线程机制实现的,而小伙伴们没有学过操作系统应用层的学习机制,更不了解操作系统内核级的线程机制,所以学完多线程有一种似会非会的感觉。跟别人交流或者面试的时候,滥用应用态切内核态、线程调度等名词。写多线程程序更是惨不忍睹。

图片

我的手写JVM小班新增的内容就是以我所理解的方式让大家精通多线程。学起来会有一点难度,所以如果你没有决心想成为一个技术大牛,建议有这个决心的时候再来,我随时都在。当然啦,底层的学习会比较枯燥,那我是怎么解决这个问题的呢?手写。沉浸在设计者的角度去思考去实现,随着对知识点本身的一点点领悟,代码的一点点成型,那种成就感是CRUD无法比拟的。给大家看下二期新增的知识大纲。

图片

如果你对JVM底层感兴趣,想要领先身边人技术一个level,欢迎加入我的手写JVM底层班,带你一起领略计算机底层的风采。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值