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

上周,有客户问我Java线程是如何运行的,我觉得有点复杂……想想看,这个Java线程后面其实还附加着一个操作系统线程,中间还有好几层。
太复杂了。

让我引导您完成创建过程:
1 初始化 Java Thread 对象:就像创建一个新对象一样简单。
设置一些属性,例如优先级以及是否为守护线程。
事情还没有开始。
JavaThread的状态是NEW,与操作系统无关。
OSThread 尚未创建。
例如,2 02 3 年,当我在上海的一家购物中心编码时,我刚刚完成一个新的 Thread,还没有调用 start。
这只是一个装饰品。

2 调用start()开始:此时一切都会发生。
start() 方法将首先检查您的线程是否已启动。
如果正在运行,再次调用就会报错。
如何保证安全启动? Hotspot使用了一种叫做Parker的东西,这是一种锁定机制,以确保创建根线程和执行java_start方法只执行一次。
例如,当我在 2 02 3 年为北京的一家公司编码时,我注意到多个线程被同时启动。
Parker锁可以防止同时创建多个操作系统线程。

3 创建操作系统线程:调用 os::create_thread,然后操作系统 API(例如 Linux 的 pthread_create)开始创建实际的操作系统线程。
一旦线程开始运行,它就会执行 java_start 方法,该方法通过 JNI 调用 Java 类的 run 方法。
一旦父线程被创建,其ID(如Linux中的pthread_t)将被存储在OSThread中(第1 8 行),以方便后续的挂起和唤醒操作。

比较复杂的状态管理:
JavaThread状态:由JVM管理,包括NEW、RUNNABLE、BLOCKED、WAITING/TIMED_WAITING、TERMINATED等。
它反映了JVM中线程的逻辑状态,如等待锁、等待条件变量等。
例如,2 02 3 年我在深圳一家公司编码时,线程调整了Object.wait,JavaThread状态变成了WAITING。
OSThread状态:由操作系统管理,如Linux的TASK_RUNNING和TASK_INTERRUPTIBLE。
例如,如前所述,在 Object.wait 之后,OSThread 状态可能会更改为 WAITING(或更准确地说,TASK_INTERRUPTIBLE)。
状态同步:如何同步? JVM 通过 JNI 调用或语义与操作系统交互。
例如,当从 RUNNABLE 变为 BLOCKED 时,JVM 告诉 OS 挂起原来的线程;释放锁后,JVM 恢复线程,JavaThread 状态变回 RUNNABLE。

解耦设计的好处:
灵活性:可以在启动之前配置线程属性,例如堆栈大小和CPU关联性。
想一想,如果你发现线程栈太小,你可能启动前调整好,无需重新启动。
安全性:避免多个调用线程同时启动引起的问题。
我以前也遇到过麻烦。
2 02 2 年,在上海的一家公司,据说两条流程同时启动。
结果,创建了两个操作系统线程,CPU 飙升至 1 00%,几乎导致服务器瘫痪。
资源控制:延迟操作系统线程的创建,以减少不必要的系统资源使用。
例如,如果您创建多个主题但只运行几个主题,这可以为您省去很多麻烦。

为什么我们需要维护两个状态机?
抽象隔离:JavaThread状态暴露给开发者,对应Java的线程生命周期; OSThread 状态暴露给操作系统,操作系统处理和调度这些基本的事情。
跨平台兼容性:不同操作系统的线程状态差异很大,JVM通过OSThread掩盖了这些差异。
性能优化:部分操作仅修改Java状态,无需与OS打交道,减少系统调用。

可分离字符串(PTHREAD_CREATE_DETACHED):
避免僵尸字符串:默认字符串是可连接的,必须手动回收;当分离线程结束时,资源会自动释放,以避免资源泄漏。
JVM管理需求:Java线程生命周期由JVM控制,无需外部回收,逻辑简化。

关键路径:
创建线程:Thread.start()→JavaThread::start()→os::create_thread()→pthread_create()。
状态同步:JavaThread::set_state()→OSThread::set_state()→调用OS API。
资源回收:当分离线程结束时,pthread_exit()将触发OSThread清理,JVM将回收JavaThread对象。

不管怎样,你看,Hotspot的设计相当复杂,但是它解决了性能和跨平台兼容性问题。
如果你很好地理解了这些细节,那么在编写多线程程序时就可以避免很多陷阱,比如线程死锁、资源泄漏等等。
但说实话,每次看这些源码,我都感觉头晕……

线程池创建的四种方法是什么

创建线程池有四种方法。
直接使用Executors工厂方法非常简单。

newCachedThreadPool:闲置 6 0 秒后回收,并在任务到达时启动新线程。
说白了,就是按需创作,无论数量多少。
上周我刚刚处理了一个高并发的HTTP请求,速度非常快。

newFixedThreadPool:固定数量的线程,满时排队。
其实这是为了避免频繁创建讨论线程。
我的项目使用 8 个线程,具体取决于 CPU 核心的数量。

newSingleThreadExecutor:单线程执行,保证顺序。
说白了,任务是一个接一个的。
适合顺序日志写入。

newScheduledThreadPool:专用于计划任务。
Planning()是延迟执行,planningAtFixedRate()是固定频率。
我怎么能这样说呢?这非常适合检测心跳。

但是说实话,Executors的默认设置并不可靠。
newFixedThreadPool无限队列尤其可怜。
我一般不建议直接使用它。
首先手动配置 ThreadPoolExecutor 设置。