游乐游手机版
首页/AI教程/文章详情

JUC并发编程入门:线程状态与Lock锁详解生产者消费者

时间:2026-08-15 14:21
大家好,我是晚安code。 JUC并发编程这门课几乎是 Ja va 后端面试的必考点——从「线程有几种状态」到「手写生产者消费者」,一问一个准。这篇就把我踩过的坑和整理好的思路一次性给你:线程状态、Lock 与 Synchronized 的区别、还有那个让无数人翻车的虚假唤醒。收藏一下,面试前翻出来

大家好,我是晚安code。

JUC并发编程这门课几乎是 Ja va 后端面试的必考点——从「线程有几种状态」到「手写生产者消费者」,一问一个准。这篇就把我踩过的坑和整理好的思路一次性给你:线程状态、Lock 与 Synchronized 的区别、还有那个让无数人翻车的虚假唤醒。收藏一下,面试前翻出来救急。

一、JUC 是什么:并发编程的「工具包」

JUC 不是一门新语言,它是 Ja va 官方写给并发编程的标准答案,藏在 ja va.util.concurrent 这个包里。

JUC(ja va.util.concurrent):Ja va 官方提供的并发编程工具包,把锁、线程池、并发集合都收在了这一个包下。你可以理解为「Ja va 并发编程的百宝箱」。

写业务逻辑时,要是还死磕普通 Thread 开线程,效率往往不尽如人意。传统 Runnable 接口本身不返回结果,想拿回数据只能靠共享变量,这不仅繁琐,还极易埋下并发隐患;而 Callable 接口支持返回值,配合 Future 才能稳妥地获取线程执行结果。JUC 的核心价值,正是把这套高效且规范的写法沉淀为标准库,让开发者无需重复造轮子。

二、线程与进程:并发和并行别搞混

并发和并行是 JUC 学习路上第一个绊脚石,分不清它,后面全是糊涂账。

先把进程和线程这两个概念捋清楚:进程可以理解成一个正在运行的程序实例,比如 QQ.exe 就算一个进程,而且任何一个进程里,至少都会有一个线程。线程则是进程内部真正负责执行任务的单元。举个更直观的例子,你在 Typora 里打字时,软件往往还会同步做自动保存——一个负责输入,一个负责保存,本质上就是两个线程在各自处理工作。至于 Ja va 程序,默认会启动两个线程:main 主线程,以及 GC 垃圾回收线程。

有个冷知识:Ja va 本身是开不了线程的。Thread.start() 底层调用的是 native start0(),这是本地方法,由底层的 C 操作操作系统去创建线程,Ja va 只负责把请求递下去。

并发:多个线程在同一段时间内交替执行同一个资源,单核 CPU 也能模拟出来。可以理解为「一条路,多辆车轮流过」。

并行:多个线程在多个 CPU 核上同时执行,真正的同时干活。可以理解为「多条路,车同时开」。

并发编程的本质就一句话:充分利用 CPU 资源。通过 Runtime.getRuntime().a vailableProcessors() 可以拿到 CPU 核数,写线程池时区分 CPU 密集型和 IO 密集型的核心线程数就用它。

并发靠 CPU 快速交替,并行靠多核同时跑

三、线程的六种状态 wait/sleep 区别

线程状态的管理,本质是 JVM 搭好的一套流转规则,记住六种状态,面试第一问基本就稳了。

Ja va 线程一共有六种状态,定义在 Thread.State 枚举里:

状态含义进入方式
NEW新生,线程刚创建没 startnew Thread()
RUNNABLE运行中,等待 CPU 调度start()
BLOCKED阻塞,竞争锁失败排队进入同步代码块
WAITING等待,一直等被唤醒wait()、join()
TIMED_WAITING超时等待,到点自己醒sleep()、wait(毫秒)
TERMINATED终止,run() 执行完正常结束

我用一张图把它串起来,面试时照着这个思路说就行:

线程状态流转图:JUC并发编程中线程的六种状态及切换条件

wait 和 sleep 是面试最爱问的一对兄弟,因为它俩长得像,本质上完全不一样。

\

我整理成一张表

对比项waitsleep
来自哪个类ObjectThread
是否释放锁释放不释放
使用范围必须在同步代码块中任何地方都能用
是否必须捕获异常不需要必须捕获

企业里写休眠推荐用 TimeUnit 工具类,比裸写 Thread.sleep 语义清晰得多:

TimeUnit.DAYS.sleep(1);// 睡 1 天TimeUnit.SECONDS.sleep(2); // 睡 2 秒

四、Lock 锁:从 synchronized 到 ReentrantLock

Lock 是 JUC 里最该先掌握的锁,它把 synchronized 的短板全补上了。先看传统写法。

传统 synchronized 解决并发问题,本质就两个字:队列 锁。多个线程同时卖票,不加锁就会超卖:

class Ticket { private int number = 50;public synchronized void sale() {// synchronized 本质:排队   加锁if (number > 0) { System.out.println(Thread.currentThread().getName()  " 卖出了 "   (number--)   " 票,剩余 "   number);}}}

synchronized 用起来省心,但它有几个短板:拿不到锁状态、线程只能一直等、锁非公平、不可中断。JUC 的 Lock 接口就是来解决这些问题的。

Lock:JUC 提供的锁接口,最常用的实现是 ReentrantLock。你可以把它想成一把「手动挡」的锁,加锁解锁都得自己来。

class Ticket2 { private int number = 50;Lock lock = new ReentrantLock();// 1. 建锁public void sale() { lock.lock();// 2. 加锁try { if (number > 0) {/* 卖票业务 */ }} finally { lock.unlock();// 3. 解锁,不写就是死锁}}}

lock 三部曲:① new ReentrantLock() 建锁 → ② lock.lock() 加锁 → ③ finally 里 lock.unlock() 解锁。第三步最关键,忘了写整个程序直接卡死。

ReentrantLock 还分公平锁和非公平锁,默认是非公平:

公平锁:先来后到,谁先排队谁先拿到锁。可以理解为食堂打饭排好队,不乱。

非公平锁:允许插队,刚释放的锁可能被后来的线程抢走(默认)。可以理解为排队时突然有人插队。

构造时传 true 就是公平锁:new ReentrantLock(true);不传参数默认非公平:new ReentrantLock()。

synchronized 和 Lock 怎么选,一张表说清楚:

对比项synchronizedLock
判断锁状态做不到tryLock() 能判断
释放锁自动释放必须手动,finally 里 unlock
等待方式线程一直等不会一直等,可超时
公平性只能非公平可设置公平/非公平
适用场景少量同步代码大量同步代码

默认非公平锁能插队,公平锁要排队,吞吐量各有利弊

五、生产者消费者问题:从 wait/notify 到 Condition

生产者消费者问题是 JUC 面试的必考题,和单例模式、排序算法、死锁并称「面试四件套」。考的不是你会不会写,而是你懂不懂等待-通知这套机制。

经典场景:一个数据类,increment 加 1、decrement 减 1,生产线程 A 加了要通知消费者 B 来减。synchronized 版本的套路是四步:判断 → 等待 → 业务 → 通知。

public synchronized void increment() throws InterruptedException { while (number != 0) { // 1. 判断:number 不是 0 就等this.wait(); // 2. 等待}number  ;// 3. 业务:加 1this.notifyAll();// 4. 通知:唤醒其他线程}

我第一次学习时写这段代码时,只有 A、B 两个线程跑得好好的,结果突发奇想我换成 A/B/C/D 四个线程,当场就翻车了——数据直接变成负数。

原因就是我用了 if 判断,这就是传说中的虚假唤醒。

虚假唤醒(Spurious Wakeup):线程没收到明确通知却被意外唤醒的现象。可以理解为「闹钟还没响,你却提前醒了」。

JDK 官方 ja vadoc 明确要求 wait 要写在循环里防虚假唤醒

if 判断一次容易翻车,while 醒来再确认一遍才安全

而 JUC 版的生产者消费者,把 wait/notify 换成了 Lock Condition,套路一模一样,只是换了 API:

Condition:和 Lock 配套的等待/通知工具,把 wait/notify 从 Object 搬到了锁上。可以理解为锁的「对讲机」,用它喊话唤醒线程。

class Data { private int number = 0;Lock lock = new ReentrantLock();Condition condition = lock.newCondition();public void increment() throws InterruptedException { lock.lock();try { while (number != 0) { condition.await();// 等待(替代 wait)}number  ;condition.signalAll();// 通知(替代 notifyAll)} finally { lock.unlock();}}}

整个流程还是「判断 → 等待 → 业务 → 通知」四步,一张图看清:

生产者消费者流程:判断等待业务通知的循环

JUC 版的好处是:Condition 可以创建多个,实现精准唤醒——比如唤醒「加 1 的」和唤醒「减 1 的」分开,比 notifyAll 一把梭精准得多,这也是 JUC 里 ArrayBlockingQueue 等并发容器底层正在用的思路。

我把整个 JUC并发编程学习笔记按这条主线整理完了:并发/并行 → 线程状态 → wait/sleep → Lock → 生产者消费者。这篇算 JUC 的地基,

我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你学 JUC 时踩过哪些并发坑?wait 和 sleep 第一次分清楚了吗?

来源:https://developer.aliyun.com/article/1753890
上一篇年阿里云服务器一年多少钱及最新价格表 下一篇课堂行为目标检测数据集:6类2000张图像标注数据
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
CAD零基础入门教程:坐标输入、图层管理与基础绘图命令
AI教程 · 2026-09-01

CAD零基础入门教程:坐标输入、图层管理与基础绘图命令

本文面向CAD零基础学习者,系统讲解坐标输入、图层管理与基础绘图命令的核心用法。通过分步实操与常见问题排查,帮助新手建立精确绘图习惯,掌握规范出图的基础能力。

CAD从入门到项目交付:绘图、标注、图块与实战工作流
AI教程 · 2026-09-01

CAD从入门到项目交付:绘图、标注、图块与实战工作流

掌握CAD的核心在于建立“画得准、标得清、复用快、交付稳”的工作流。本文提供从环境设置、高频命令组合、标注规范、图块标准化到项目分阶段交付的完整路径,帮助初学者避免常见返工陷阱,独立完成可检查、可复用、可打印的工程图纸。

Claude Code 登录指南:个人、Teams 与企业账号区分与授权步骤
AI教程 · 2026-09-01

Claude Code 登录指南:个人、Teams 与企业账号区分与授权步骤

本文详细解析 Claude Code 登录前的账号类型区分方法,涵盖个人订阅、Teams 席位与企业 Enterprise 席位的授权路径差异。提供终端登录命令、环境变量排查及常见异常处理步骤,帮助用户快速完成正确授权并避免登录路径混淆。

Claude Code 文件修改前的权限模式配置与命令审批指南
AI教程 · 2026-09-01

Claude Code 文件修改前的权限模式配置与命令审批指南

本文详细介绍Claude Code在修改文件前的权限模式配置方法,包括defaultMode可选值、permissions allow与deny规则设置、多层级配置文件管理以及 status验证技巧,帮助开发者安全高效地使用AI编程助手。

Claude Code接入VS Code后先测扩展和终端命令
AI教程 · 2026-09-01

Claude Code接入VS Code后先测扩展和终端命令

在VS Code中接入Claude Code后,建议优先验证扩展面板与集成终端两条入口。本文提供标准检查顺序、关键命令与常见故障排查路径,帮助你快速确认环境就绪,避免后续开发受阻。