
这一篇把原始笔记里关于「线程是什么」的部分单独收拢。面试里从进程、线程问起,真正想确认的通常不是定义背诵,而是能不能从线程状态解释线上卡顿、从资源边界解释并发上限。
进程和线程的区别
进程是操作系统分配资源的基本单位:地址空间、打开的文件、堆、全局数据都归进程所有。线程是进程内的执行单位,同一进程的线程共享堆和大部分运行时资源,但各自拥有程序计数器、虚拟机栈与本地方法栈。
因此,线程切换通常比进程切换轻,但并不免费。线程数量增加后,调度、栈内存、上下文切换和锁竞争都会成为成本。把一个任务「改成多线程」并不会自动变快;先要判断它是 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,随后被 notify、notifyAll、中断或超时唤醒,再重新竞争锁。
所以 wait 的正确形态永远围绕条件循环,而不是一次 if:
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait();
}
return queue.remove();
}
循环不是形式主义:它处理虚假唤醒,也处理多个消费者被唤醒后只有一个能真正拿到元素的情况。生产代码中,BlockingQueue、Condition、CountDownLatch 往往更适合直接表达意图;理解 wait/notify 仍然必要,因为它解释了 monitor 与条件等待的基本语义。
什么是上下文切换
当 CPU 从一个线程切到另一个线程,需要保存和恢复寄存器、程序计数器、栈等执行现场;被动切换可能来自时间片耗尽、I/O 阻塞、锁竞争,主动切换可能来自 yield、等待条件或任务提交。
高并发服务中,上下文切换异常多通常不是一个孤立指标。常见原因是线程池排队、锁竞争、短任务被拆得过细,或者无意义的忙等。先用线程 dump 和火焰图确认线程实际在做什么,再决定是否需要减小并发、缩短临界区或调整任务粒度。
死锁产生的条件
经典死锁需要同时满足互斥、请求并保持、不可剥夺、循环等待。工程上最有效的预防方式是让资源按固定全局顺序获取,例如始终按订单 ID 从小到大加锁。对可降级路径,可使用 tryLock 加超时并设计失败后的回退。
发生疑似死锁时,先抓取 jstack:它能给出线程在等待哪个锁、哪个线程持有该锁,以及是否存在循环。日志只能描述症状,线程 dump 才能验证等待关系。
小结
线程模型决定的是执行和等待如何发生,不是系统可以无限并发的许可证。回答线程题时,把「状态—正在等什么—谁在持有资源—下游是否能承受」连起来,比单独列六种状态更接近实际排障。
FIELD NOTES / DISCUSS
文章讨论
读完后,欢迎留下你的补充、疑问或不同看法。
正在读取评论…