线程调用start()后进入操作系统就绪队列等待调度,用户态无法直接观测该队列,但可通过thread.state、纳秒时间戳、多线程竞争、thread.onspinwait()及/proc接口间接印证其存在与行为。

线程调用 start() 后,并不会立即执行,而是由 JVM 向操作系统申请内核线程资源,之后该线程进入操作系统的就绪队列(ready queue);当调度器选中它时,才被放入运行队列(running state,即在某个 CPU 核心上实际执行)。这个“漂移”过程是操作系统调度行为,**用户态代码无法直接观测或控制就绪/运行队列的内部状态**,但可以通过间接方式演示和印证其存在——比如利用高竞争、短任务、时间戳与线程状态配合,观察调度延迟与状态跃迁。
用 Thread.State 配合纳秒级时间戳捕获状态跃迁
Java 的 Thread.getState() 能反映 JVM 对线程状态的抽象视图(如 RUNNABLE 表示“就绪或运行中”,不区分 OS 就绪队列与运行中),结合 System.nanoTime() 可估算调度延迟:
-
RUNNABLE状态持续期间,线程可能在就绪队列排队,也可能正在 CPU 上执行; - 若线程刚
start()后立刻查状态,大概率仍是NEW或刚变RUNNABLE,但真实执行可能滞后数微秒至毫秒; - 用循环高频轮询
getState()并记录首次变为RUNNABLE的时刻,再与任务体内第一行代码的时间戳对比,差值即为“就绪等待时间”(含内核调度延迟)。
用多线程竞争 + Thread.onSpinWait() 暴露就绪排队现象
创建远超 CPU 核心数的活跃线程(如 100 个忙循环线程),迫使大量线程长期处于就绪态但得不到 CPU 时间片。此时可观察到:
- 每个线程的
System.nanoTime()累加速度远低于单线程(说明不是一直在运行); - 用
ThreadMXBean查询getThreadCpuTime(id)与getThreadUserTime(id),二者差值小,但getThreadAllocatedBytes()仍在增长(说明线程被频繁切换进出运行态); - 插入
Thread.onSpinWait()(提示 CPU 当前在自旋等待条件,有助于调度器识别“非真正忙等”),可轻微改善就绪队列滞留时间——这是对底层调度策略产生影响的间接证据。
用 Linux /proc 接口佐证(需运行在 Linux 环境)
Java 进程中的每个线程对应一个 task(轻量级进程 LWP),可通过 /proc/[pid]/task/[tid]/stat 查看内核级状态:
- 字段第 3 项是状态码:
R表示 running 或 runnable(即在就绪队列或运行中),S是 sleep,D是 uninterruptible sleep; - 字段第 14、15 项是 utime/stime(用户态/内核态 jiffies),若长时间不变而状态一直是
R,说明它在就绪队列排队; - 写一段 Java 程序启动线程后,用
ProcessBuilder定期读取对应/proc/self/task/*/stat,解析并打印状态与时间字段变化,就能看到某线程反复处于R态但 utime 几乎不动——这就是“漂移”在就绪队列的实证。
关键提醒:没有“实时可视化队列”的 API
操作系统不会向用户进程暴露就绪队列的内存结构或排序逻辑(出于安全与抽象隔离)。所谓“演示漂移”,本质是通过可观测指标(状态、时间、CPU 时间、/proc 数据)反推调度行为。不要尝试用 synchronized 或 LockSupport.park() 来“模拟”就绪队列——那是阻塞队列,不是 OS 调度队列。真正的就绪队列由内核维护,用户态只能感知其效果,不能操纵其内容。











