前言

本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。

线程状态切换机制剖析

在 Java 虚拟机(HotSpot)的架构中,Java 语言层面的线程状态(java.lang.Thread.State)与 JVM 内部实现的状态(JavaThreadState)并非一一对应。Java 线程的状态切换不仅涉及 JVM 内部状态机的流转,还深度依赖操作系统的底层同步原语(如 Pthreads、互斥锁、条件变量)以及 HotSpot 的安全点(Safepoint)机制。

作为程序员,理解 Java 线程状态切换的核心在于明白:Java 层的 java.lang.Thread.State 只体现了一个高层的抽象状态,而 JVM 内部(HotSpot)有一套更复杂的 JavaThreadState,它们最终都依赖操作系统的 OS 线程状态(如 Linux 的 TASK_RUNNING、TASK_INTERRUPTIBLE)来实现。

在 OpenJDK 8中,Java 线程采用的是 1:1 模型,即一个 Java 线程对应一个 JVM 内部的 JavaThread,并对应一个操作系统的原生线程(Native Thread)。

以下将结合 OpenJDK 8源码,从线程创建、就绪、阻塞、等待到销毁的核心生命周期,深度剖析其状态切换机制。


一、 线程状态映射:Java 状态 vs JVM 内部状态

在阅读源码前,需明确两种状态枚举的对应关系:

  1. Java 层状态 (java.lang.Thread.State):NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
  2. JVM 内部状态 (src/share/vm/utilities/globalDefinitions.hpp):
enum JavaThreadState {
  _thread_uninitialized     =  0, // 线程正在创建
  _thread_new               =  2, // 已创建但尚未启动
  _thread_in_native         =  4, // 正在执行本地代码(JNI)
  _thread_in_vm             =  6, // 正在 JVM 内部执行(如 GC 准备、类加载)
  _thread_in_Java           =  8, // 正在执行解释或编译的 Java 字节码
  _thread_blocked           = 10, // 阻塞状态(等待锁、条件变量、或安全点 Safepoint 挂起)
  ...
};


二、 核心状态切换源码分析

1. NEW → \rightarrow RUNNABLE:线程的创建与启动

当在 Java 中调用 thread.start() 时,本地方法 JVM_StartThread 会被触发。这一步完成了从 NEW 到 JVM 内部 _thread_new 再到 OS 线程拉起的全过程。

源码剖析一:src/share/vm/prims/jvm.cpp (JVM_StartThread)
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JavaThread *native_thread = NULL;
  
  // 1. 检查线程是否已经启动过,防止重复启动
  if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
    throw_illegal_thread_state = true;
  }

  if (!throw_illegal_thread_state) {
    // 获取期望的栈大小
    jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
    
    // 2. 创建 JVM 内部的 JavaThread 对象
    // thread_entry 是线程启动后真正执行的高层回调函数(执行 run 方法)
    native_thread = new JavaThread(&thread_entry, size);
    
    if (native_thread->osthread() != NULL) {
      // 3. 此时线程已成功与 OS 线程绑定,设置其内部状态为 _thread_new
      native_thread->prepare(jthread);
    }
  }

  // ... 省略部分校验逻辑 ...

  // 4. 真正唤醒并运行操作系统的原生线程
  Thread::start(native_thread);

JVM_END

源码剖析二:src/os/linux/vm/os_linux.cpp (os::create_thread)

new JavaThread() 内部,会调用具体操作系统的创建方法。在 Linux 下通过 pthread_create 实现:

bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  // 1. 分配 OSThread 结构体,用于记录底层 OS 线程句柄和状态
  OSThread* osthread = new OSThread(NULL, NULL);
  thread->set_osthread(osthread);

  // 初始化底层线程状态为 ALLOCATED(已分配)
  osthread->set_state(ALLOCATED);

  pthread_t tid;
  // 2. 调用 Linux 的 pthread_create 创建底层原生线程
  // java_start 是底层的统一线程启动入口
  int ret = pthread_create(&tid, &attr, java_start, thread);
  
  if (ret != 0) {
    // 创建失败处理...
    return false;
  }

  // 保存系统级线程 ID (TID)
  osthread->set_pthread_id(tid);
  return true;
}

源码剖析三:src/share/vm/runtime/thread.cpp (Thread::start)

此时底层的 pthread 已经创建,但处于同步等待状态(通过信号量或互斥量挂起),直到 Thread::start 被调用:

void Thread::start(Thread* thread) {
  trace_thread_start(thread, true);
  
  // 获取 OSThread 指针
  OSThread* osthread = thread->osthread();
  
  // 更改底层 OS 线程状态为 INITIALIZED 
  osthread->set_state(INITIALIZED);
  
  // 核心:唤醒在 java_start 中因为等待同步信号而挂起的底层线程
  // 内部通常使用 os::PlatformEvent->unpark() 或 sem_post
  os::start_thread(thread);
}

系统工程师视角: Java 线程的 RUNNABLE 状态在 JVM 层面细分为 _thread_in_Java(正在执行字节码)和 _thread_in_native(执行 JNI)。在 Linux OS 层面,无论线程是在计算还是在等待 CPU 时间片,其底层状态均是 TASK_RUNNING


2. RUNNABLE ↔ \leftrightarrow BLOCKED:synchronized 锁竞争

当 Java 线程试图进入 synchronized 块且锁已被占用时,线程状态转换为 BLOCKED。这涉及到 HotSpot 的重量级锁实现 ObjectMonitor

源码剖析四:src/share/vm/runtime/objectMonitor.cpp (ObjectMonitor::EnterI)

当轻量级锁膨胀为重量级锁,或者直接竞争失败时,线程进入 EnterI 方法:

void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    assert (Self->is_Java_thread(), "invariant") ;
    JavaThread * jt = (JavaThread *) Self ;

    // 1. 尝试再次构造一次轻量级的自旋竞争锁,减少挂起代价
    if (TryLock (Self) > 0) { return; }

    // 2. 将当前线程包装成 ObjectWaiter 节点
    ObjectWaiter node(Self) ;
    Self->_ParkEvent->Reset() ;
    node.TState = ObjectWaiter::TS_CXQ ;

    // 3. 将节点原子地(CAS)推入锁的竞争队列 _cxq
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
        if (TryLock (Self) > 0) { return; } // 双重检查
    }

    // 4. 【状态切换核心】改变 JVM 内部状态为 _thread_blocked 
    // 这会让安全点(Safepoint)机制感知到该线程已阻塞,不需要等待它
    ThreadBlockInVM tbivm(jt);

    // 5. 进入操作系统级别的挂起循环
    for (;;) {
        if (TryLock (Self) > 0) break ; // 再次尝试获取锁

        // 检查是否有中断请求
        if (Self->is_interrupted(true)) {
            // 中断逻辑处理...
        }

        // 6. 调用操作系统底层的 park() 挂起线程
        // 在 Linux 上,底层是通过 pthread_cond_wait 或 OS 信号量实现,进入 TASK_UNINTERRUPTIBLE/TASK_INTERRUPTIBLE 状态
        Self->_ParkEvent->park() ;

        // 被唤醒(Unpark)后,继续循环尝试 TryLock
        // 只有拿到锁,才能跳出循环,退出 ThreadBlockInVM 作用域,恢复 _thread_in_Java 状态
    }
}


3. RUNNABLE ↔ \leftrightarrow WAITING / TIMED_WAITING:显式挂起

Java 显式挂起主要通过两种途径:Object.wait()(配合重量级锁)和 LockSupport.park()(配合 JUC 锁)。

途径 A:Object.wait() 机制
源码剖析五:src/share/vm/runtime/objectMonitor.cpp (ObjectMonitor::wait)
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
   Thread * Self = THREAD ;
   JavaThread * jt = (JavaThread *) Self ;

   // 1. 构造 ObjectWaiter 节点,状态置为 TS_WAIT (等待队列)
   ObjectWaiter node(Self);
   node.TState = ObjectWaiter::TS_WAIT ;

   // 2. 释放当前持有的重量级锁(因为 wait 必须在 synchronized 内调用)
   int     revocation_count = 0;
   __waiters++; 
   AddWaiter (&node) ; // 假如到 _WaitSet 队列中
   Exit (true, Self) ; // 释放锁,允许其他 EnterI 的线程进来

   // 3. 核心:转换 JVM 线程状态为阻塞/等待
   // 注意:若传入的 millis > 0,Java 层表现为 TIMED_WAITING,否则为 WAITING
   // 但在 JVM 内部统一使用 ThreadBlockInVM 封装
   ThreadBlockInVM tbivm(jt);

   if (millis == 0) {
      // 永久等待:调用底层操作系统的 park()
      Self->_ParkEvent->park() ;
   } else {
      // 限时等待:调用底层操作系统的 timed_park()
      Self->_ParkEvent->park(millis) ;
   }

   // 4. 线程被 notify() 唤醒或者超时醒来后,必须重新去竞争锁
   // 此时状态其实又退回到了类似同步块竞争的逻辑
   ObjectMonitor::EnterI (THREAD) ; 
}

途径 B:LockSupport.park() 机制

j.u.c 包下的核心 AQS 依赖 Unsafe.park。它不依赖 ObjectMonitor,而是直接操作线程的 Parker 对象。

源码剖析六:src/os/posix/vm/os_posix.cpp (Parker::park)

以 POSIX(Linux/Unix)系统为例,分析底层的系统级原语调用:

void Parker::park(bool isAbsolute, jlong time) {
  // 1. 如果此前已经有 unpark() 产生的许可(counter > 0),直接消耗掉许可并返回
  if (Atomic::xchg(0, &_counter) > 0) return;

  Thread* thread = Thread::current();
  JavaThread *jt = (JavaThread *)thread;

  // 如果线程已被中断,直接返回
  if (jt->is_interrupted(false)) return;

  // 2. 计算超时时间
  struct timespec absTime;
  if (time > 0) {
     compute_abstime(&absTime, isAbsolute, time);
  }

  // 3. 变更 JVM 线程状态
  ThreadBlockInVM tbivm(jt);

  // 4. 进入底层互斥锁和条件变量的等待
  pthread_mutex_lock(_mutex);
  
  if (_counter > 0) { // 双重检查许可
     _counter = 0;
     pthread_mutex_unlock(_mutex);
     return;
  }

  // 5. 执行 OS 级挂起
  if (time == 0) {
    // 对应 Java 的 WAITING 状态
    // 调用 Linux 原生 POSIX 线程库,线程进入睡眠状态
    pthread_cond_wait(_cond, _mutex);
  } else {
    // 对应 Java 的 TIMED_WAITING 状态
    pthread_cond_timedwait(_cond, _mutex, &absTime);
  }

  // 被唤醒后,重置许可
  _counter = 0;
  pthread_mutex_unlock(_mutex);
}


4. TERMINATED:线程的销毁与资源回收

当 Java 的 run() 方法执行完毕或者抛出未捕获异常退出时,线程开始走向 TERMINATED 状态。

源码剖析七:src/share/vm/runtime/thread.cpp (JavaThread::exit)
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  
  // 1. 触发 Java 层的 Thread.exit() 方法,清理 ThreadLocal 等资源
  if (!this->has_pending_exception() &&          
      juint(chunks) == 0 &&                      
      java_lang_Thread::thread(threadObj()) == this) {
     // 通过虚拟机内部调用机制执行 java.lang.Thread.exit()
     JavaValue result(T_VOID);
     JavaCalls::call_virtual(&result, threadObj, ...);
  }

  // 2. 通知 JVM 的线程服务监视器(ThreadService),此处会将高层状态变更为 TERMINATED
  ThreadService::current_thread_status_changed(this, java_lang_Thread::TERMINATED);

  // 3. 移除锁资源、释放 JNI 句柄块
  assert(!has_last_Java_frame(), "this must be remove");
  
  // 4. 从全局线程列表中移除当前线程(需要获取 Threads_lock 锁)
  Threads::remove(this);

  // 5. 彻底释放底层 OSThread 
  this->set_osthread(NULL);
  
  // 6. 调用底层操作系统退出原语(如 pthread_exit),OS 线程生命周期终结
  os::free_thread(osthread);
}


三、 系统级视角:总结与映射表

从系统工程师的角度来看,Java 线程状态的切换是一个典型的分层抽象与状态映射设计。

Java 线程状态 (Thread.State) JVM 内部状态 (JavaThreadState) 底层 OS 线程状态 (以 Linux 为例) 触发的关键源码/原语
NEW _thread_uninitialized 无 / ALLOCATED new Thread(), 内部调用 pthread_create
RUNNABLE _thread_in_Java / _thread_in_native TASK_RUNNING (正在运行或在就绪队列中) Thread.start() → \rightarrow os::start_thread
BLOCKED _thread_blocked TASK_INTERRUPTIBLE / TASK_UNINTERRUPTIBLE ObjectMonitor::EnterI → \rightarrow PlatformEvent::park()
WAITING _thread_blocked TASK_INTERRUPTIBLE ObjectMonitor::wait(0)Parker::park(0) → \rightarrow pthread_cond_wait
TIMED_WAITING _thread_blocked TASK_INTERRUPTIBLE Parker::park(time) → \rightarrow pthread_cond_timedwait
TERMINATED _thread_uninitialized (已被移出列表) EXIT_ZOMBIE / 销毁 JavaThread::exit → \rightarrow pthread_exit

核心机制沉淀:

  1. ThreadBlockInVM 的妙用:在任何涉及线程可能挂起的操作(如等锁、等条件变量)前,JVM 都会显式声明一个 ThreadBlockInVM 作用域。这个类的构造函数会把当前线程的状态改为 _thread_blocked。这样,当垃圾回收器(GC)发起安全点(Safepoint)同步时,看到该线程已经是 _thread_blocked 状态,就知道它绝对不会修改 Java 堆和寄存器,从而无需等待该线程清醒即可直接进入 GC 阶段。这是一种极致的并发异步优化。
  2. 1:1 模型的性能损耗:从 RUNNABLE 转换为 BLOCKED/WAITING,其底层不可避免地调用了 pthread_cond_wait 等 POSIX 原语,这触发了严重的从用户态到内核态的上下文切换(Context Switch),涉及到 CPU 寄存器恢复、页表缓存(TLB)部分失效等,这也是重量级锁以及传统 Java 线程切换开销大的根本原因。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐