搜索站内内容

← 返回文章列表

JUC 学习 02:进程、线程、虚拟线程、线程状态

围绕进程与线程、六种线程状态、sleep/wait、死锁与上下文切换整理 JUC 笔记中的基础问题。

JasonBackendJavaJUCThreadVirtual ThreadDeadlock约 1,150 字大约 3 分钟
云雾在层叠的山岭之间流动

这一篇把原始笔记里关于「线程是什么」的部分单独收拢。面试里从进程、线程问起,真正想确认的通常不是定义背诵,而是能不能从线程状态解释线上卡顿、从资源边界解释并发上限。

进程和线程的区别

进程是操作系统分配资源的基本单位:地址空间、打开的文件、堆、全局数据都归进程所有。线程是进程内的执行单位,同一进程的线程共享堆和大部分运行时资源,但各自拥有程序计数器、虚拟机栈与本地方法栈。

因此,线程切换通常比进程切换轻,但并不免费。线程数量增加后,调度、栈内存、上下文切换和锁竞争都会成为成本。把一个任务「改成多线程」并不会自动变快;先要判断它是 CPU 密集,还是在等待 I/O、锁或下游服务。

协程把调度进一步下沉到用户态;Java 的虚拟线程则由运行时把大量虚拟线程复用到较少的平台线程上。它适合大量阻塞 I/O,例如同时等待 HTTP、数据库或消息队列响应。它不改变 CPU 核数,也不会放大数据库连接池、下游限流和内存等有限资源。

Java 线程有哪些状态

Thread.State 有六种状态:

状态含义排查时先看什么
NEW已创建、未启动是否遗漏 start()
RUNNABLE可运行或正在运行CPU、热点栈帧、是否自旋
BLOCKED等待进入 synchronized被谁持有 monitor
WAITING无限期等待等待的条件、队列或锁
TIMED_WAITING有超时的等待timeout 是否合理
TERMINATED已结束异常、任务生命周期

RUNNABLE 不等于「正在吃满 CPU」:它也可能正在等待系统调用返回;WAITING 也不一定是坏事,线程池消费者等待队列恰好是正常行为。诊断时要把线程状态、栈顶方法、CPU 使用率、请求量放在一起看。

sleep 和 wait 的区别

Thread.sleep 是静态方法:它让当前线程暂停,但不会释放已经持有的 monitor。Object.wait 必须在持有该对象 monitor 时调用;进入等待后会释放 monitor,随后被 notifynotifyAll、中断或超时唤醒,再重新竞争锁。

所以 wait 的正确形态永远围绕条件循环,而不是一次 if

synchronized (queue) {
  while (queue.isEmpty()) {
    queue.wait();
  }
  return queue.remove();
}

循环不是形式主义:它处理虚假唤醒,也处理多个消费者被唤醒后只有一个能真正拿到元素的情况。生产代码中,BlockingQueueConditionCountDownLatch 往往更适合直接表达意图;理解 wait/notify 仍然必要,因为它解释了 monitor 与条件等待的基本语义。

什么是上下文切换

当 CPU 从一个线程切到另一个线程,需要保存和恢复寄存器、程序计数器、栈等执行现场;被动切换可能来自时间片耗尽、I/O 阻塞、锁竞争,主动切换可能来自 yield、等待条件或任务提交。

高并发服务中,上下文切换异常多通常不是一个孤立指标。常见原因是线程池排队、锁竞争、短任务被拆得过细,或者无意义的忙等。先用线程 dump 和火焰图确认线程实际在做什么,再决定是否需要减小并发、缩短临界区或调整任务粒度。

死锁产生的条件

经典死锁需要同时满足互斥、请求并保持、不可剥夺、循环等待。工程上最有效的预防方式是让资源按固定全局顺序获取,例如始终按订单 ID 从小到大加锁。对可降级路径,可使用 tryLock 加超时并设计失败后的回退。

发生疑似死锁时,先抓取 jstack:它能给出线程在等待哪个锁、哪个线程持有该锁,以及是否存在循环。日志只能描述症状,线程 dump 才能验证等待关系。

小结

线程模型决定的是执行和等待如何发生,不是系统可以无限并发的许可证。回答线程题时,把「状态—正在等什么—谁在持有资源—下游是否能承受」连起来,比单独列六种状态更接近实际排障。

FIELD NOTES / DISCUSS

文章讨论

读完后,欢迎留下你的补充、疑问或不同看法。

48 浏览

全部评论 (0)

正在读取评论…

    GUEST IDENTITY

    设置访客身份

    评论、回复与留言板将复用这份身份。

    选择头像