JUC并发编程入门:从线程状态到Lock锁,一文吃透生产者消费者

简介: JUC并发编程是Java面试常考点。本文用学习笔记视角,理清线程状态、Lock与Synchronized的区别,手写生产者消费者并解决虚假唤醒。

大家好,我是晚安code。

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

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

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

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

自己写业务时,用普通 Thread 开线程效率并不高。传统 Runnable 接口没有返回值,回调结果全靠共享变量,又麻烦又容易出并发问题;Callable 接口能返回结果,配合 Future 才能拿到线程的返回值。JUC 就是把这一套高效写法给做成了标准库。

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

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

先说进程和线程:进程是一个程序的集合,比如 QQ.exe 就是一个进程,一个进程至少包含一个线程。线程是进程里的执行单元,比如你用 Typora 打字的同时它还在自动保存——写字和保存就是两个线程在干活。Java 程序默认开两个线程:main 主线程和 GC 垃圾回收线程。

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

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

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

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

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

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

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

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

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

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

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

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

我整理成一张表

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

企业里写休眠推荐用 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() 加锁 → ③ finallylock.unlock() 解锁。第三步最关键,忘了写整个程序直接卡死。

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

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

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

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

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

对比项 synchronized Lock
判断锁状态 做不到 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. 业务:加 1
    this.notifyAll();          // 4. 通知:唤醒其他线程
}

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

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

可能有人会问:什么情况会被虚假唤醒?

当有多个线程同时 wait 在同一个条件上时,某个线程可能没收到通知、没被中断、也没超时,却被莫名唤醒。Java 官方文档明确建议:wait 必须放在循环里(while 判断),等条件真正满足再继续。把 if 改成 while 就彻底解决——每次醒来重新判断一次,不满足就继续等。

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

JDK 官方 javadoc 明确要求 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 第一次分清楚了吗?

目录
相关文章
|
6天前
|
机器学习/深度学习 人工智能 缓存
DeepSeek V4 Flash 对标 Gemini 3.6,AI 大跑毒时代
DeepSeek V4 Flash 0731 上线公测,智能指数追平 Gemini 3.6 Flash,价格仅其零头,拆解「大跑毒时代」谁先出局。
220 0
DeepSeek V4 Flash 对标 Gemini 3.6,AI 大跑毒时代
|
2月前
|
前端开发 Java Nacos
Nacos 注解全解析:7 个核心注解 + 5 个生产踩坑清单(2026 实测)
Nacos 注解用错,是 90% 微服务 Bug 的根源。本文把注册发现、配置热更新的 7 个核心注解、动态刷新原理和 5 个生产踩坑一次讲透,附可运行代码。
368 2
Nacos 注解全解析:7 个核心注解 + 5 个生产踩坑清单(2026 实测)
|
1月前
|
算法 Java Nacos
Sentinel 流量治理实战:熔断降级限流 + Nacos 持久化
Sentinel 流量治理如何落地?本文从服务雪崩讲起,带你掌握熔断、降级、限流策略,配合 Nacos 实现规则持久化
213 0
|
1月前
|
SQL 缓存 运维
RBAC 权限模型实战:从 ACL 踩坑到 RBAC3 落地(2026)
写过 ACL 才懂 RBAC 的好。本文把 RBAC 权限模型、RBAC0~3、角色继承、互斥约束、用户组、四类权限粒度一次讲透,并附可落地的表设计
159 0
|
3天前
|
JSON 自然语言处理 Java
Elasticsearch查询实战:DSL与JavaRestClient一学就会
Elasticsearch查询离不开DSL语法和JavaRestClient:match、term、bool、分页、聚合一次讲透,看完就能写代码。
33 1
Elasticsearch查询实战:DSL与JavaRestClient一学就会
|
4天前
|
消息中间件 缓存 NoSQL
RocketMQ 顺序消费实战:批量接口加 Redis Pipeline同步数据
用户数据同步总乱序、缓存写入又慢?本文用 RocketMQ 顺序消费、批量接口、Redis Pipeline 三件套讲清同步链路。
60 1
RocketMQ 顺序消费实战:批量接口加 Redis Pipeline同步数据
|
5天前
|
消息中间件 缓存 NoSQL
关注功能高并发怎么扛?Redis ZSet + Lua + MQ 异步落库一整套
关注功能高并发怎么设计?本文拆解 Redis ZSet 存关系、Lua 保原子性、MQ 异步落库、消费端令牌桶削峰四环,抗住瞬间洪峰。
47 1
|
6天前
|
消息中间件 缓存 NoSQL
RocketMQ 延迟消息实战:延迟双删策略解决 Redis 缓存一致性
RocketMQ 延迟消息怎么用?本文从本地搭建开始,用订单超时案例讲透延迟消息与延迟双删,解决 Redis 缓存一致性难题。
66 2
|
10天前
|
设计模式 算法 Java
干掉成山的 if-else:工厂造、策略选,一文讲透两个模式的配合
写给被成堆 if-else 折磨的后端同学:拆开工厂模式和策略模式,搞懂创建与选择的解耦,配合 Spring 实战消灭分支地狱。
76 1
干掉成山的 if-else:工厂造、策略选,一文讲透两个模式的配合
|
1天前
|
JSON 运维 Java
Java 异常与日志规范:错误码、异常处理、日志规约一次讲清
Java 异常与日志规范怎么落地?本文从错误码、异常处理、日志规约三个维度,给出一套让线上问题 10 分钟定位的规范,附正反例代码。
18 0
Java 异常与日志规范:错误码、异常处理、日志规约一次讲清