hello,小伙伴们好,我是江湖人送外号[道格牙]的子牙老师。
近期准备给大家分享专题系列文章,聚焦Java多线程机制。hotspot其他模块的文章准备先放一放。会从hotspot源码角度,给大家揭秘平时学习多线程那些从来没有想过的问题,或者存在疑虑却又无法证明的理论。当然,如果你有好的问题,可以私信分享给我。
今天是系列文章首篇,咱们来谈谈Java线程创建的一些细节问题:
- Java线程是如何与OS线程建立联系的
- Java线程与OS线程共用一套线程状态吗
- Java线程是如何做到创建与启动分开的
- Java线程在JVM层面为什么要有JavaThread与OSThread
- Java线程为什么设计的时候要将创建与启动分开
- ……
先把这五个问题搞明白吧,其他的后面的文章再分享。本篇文章的观点都聚焦于Linux平台,不适用于所有平台。因为不同OS,底层差异还是挺大的。
Linux线程创建
线程能力是操作系统才有的,固Java的线程机制一定是基于OS的线程机制实现的,加上些许JVM自身的考虑在其中。这些考虑在哪能看到?JSR规范中。
上一段Linux平台下创建线程的代码
这样创建出来的线程,一般称为原生线程,或native thread。Java的线程实现其实就是将Linux下的线程机制基于JSR规范进行设计重组。如果我们了解Linux的线程机制,并搞明白了JVM是如何进行设计重组的。那么,Java的多线程,我们就算真正学明白了。
可以发现,跟Java创建线程明显不同的是:原生线程创建与运行是一体的,即线程创建完毕马上就运行。而Java中创建归创建,调用start线程才运行。
Java线程与原生线程之间是这样的关系:JavaThread->OSThread->native thread。后面会结合hotspot源码细讲。
Java线程创建
上一段创建Java线程的代码
从研究问题的角度,这段我们习以为常的代码要分成两部分来看:一、创建一个Java对象。注意,这一步只是单纯地创建一个Java对象,并没有什么特殊处理在里面。二、调用start方法让线程运行。我们上面提的几个问题,所有的秘密都在这一步中。接下来我结合hotspot源码将上面几个问题的答案分享给大家。
第一个问题:Java线程是如何与OS线程建立联系的,先上代码,这里只保留了关键代码
解释下上面的代码:
- 第2句创建了一个OSThread对象。第4句将JavaThread与OSThread建立联系。
- 第16句是创建原生线程。这一步执行完,新创建的线程就会马上执行java_start方法,java_start方法最终会通过JNI调用Java代码中的run方法。
- 第18句是将原生线程的ID存储到OSThread中。因为Linux下所有操作线程的API都需要传入线程ID。
- 总结来说,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() 操作。
我的困惑
Java的线程机制我觉得设计的过于复杂了,两个方面:一、需要维护两套线程状态;二、创建与启动分开。
目前不太理解JVM为什么要这样设计,所以我在手写JVM的课程中,我准备反其道而行,看看这样实现存在什么样的问题或者不便,这些问题或不便应该就是JVM这样设计的理由。这也就是我开设手写JVM小班的意义之一,你看到的所有理论,你心中的所有猜疑,如果你有一个自己手写自己熟悉的JVM,你就可以随时去论证,找到正确答案。而不是模棱两可、自己都没有底气的猜疑。
有些小伙伴可能想,我直接改hotspot源码不也可以吗?的确,可以,但是这个门槛太高了。你如果没有能力手写一个JVM,改hotspot源码那就是天方夜谭了。