Java线程创建过程中的各种细节

简介: 哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。

哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。
手撸过JVM、内存池、垃圾回收算法、synchronized、线程池、NIO、三色标记算法…

erafbbd8u7.png

今天咱们来谈谈Java线程创建的一些细节问题:

  1. Java线程是如何与OS线程建立联系的
  2. Java线程与OS线程共用一套线程状态吗
  3. Java线程是如何做到创建与启动分开的
  4. Java线程在JVM层面为什么要有JavaThread与OSThread
  5. Java线程为什么设计的时候要将创建与启动分开
  6. ……

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

Linux线程创建

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

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

image.png

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

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

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

Java线程创建

上一段创建Java线程的代码

image.png

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

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

image.png

解释下上面的代码:

  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也有线程状态。这个问题后面写篇文章细讲。

image.png

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

image.png

如果你对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源码那就是天方夜谭了。

我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。如果你也喜欢研究底层,欢迎关注我的公众号【硬核子牙】

相关文章
|
6天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
17天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
79 6
【Java学习】多线程&JUC万字超详解
|
2天前
|
Java
深入理解Java中的多线程编程
本文将探讨Java多线程编程的核心概念和技术,包括线程的创建与管理、同步机制以及并发工具类的应用。我们将通过实例分析,帮助读者更好地理解和应用Java多线程编程,提高程序的性能和响应能力。
15 4
|
10天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
2天前
|
安全 Java 调度
Java 并发编程中的线程安全和性能优化
本文将深入探讨Java并发编程中的关键概念,包括线程安全、同步机制以及性能优化。我们将从基础入手,逐步解析高级技术,并通过实例展示如何在实际开发中应用这些知识。阅读完本文后,读者将对如何在多线程环境中编写高效且安全的Java代码有一个全面的了解。
|
10天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
7天前
|
Java 调度 开发者
Java中的多线程基础及其应用
【9月更文挑战第13天】本文将深入探讨Java中的多线程概念,从基本理论到实际应用,带你一步步了解如何有效使用多线程来提升程序的性能。我们将通过实际代码示例,展示如何在Java中创建和管理线程,以及如何利用线程池优化资源管理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧,帮助你更好地理解和应用多线程编程。
|
12天前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用
|
11天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
16天前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。