news 2026/5/25 5:28:13

JUC(java.util.concurrent)完整学习笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JUC(java.util.concurrent)完整学习笔记

# JUC(java.util.concurrent)完整学习笔记 ## 整体架构

┌─────────────────────────────────────────────────────────────────────────────┐
│ JUC 四层架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 第四层:应用层(开箱即用) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ConcurrentHashMap | CopyOnWriteList | BlockingQueue │ │
│ │ ThreadPoolExecutor | ForkJoinPool | CompletableFuture │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↑ │
│ 第三层:同步器(基于 AQS 扩展) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ReentrantLock | Semaphore | CountDownLatch | ReadWriteLock │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↑ │
│ 第二层:AQS + 原子类(核心框架) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ AbstractQueuedSynchronizer(state + CLH队列 + 模板方法) │ │
│ │ AtomicInteger | AtomicReference | LongAdder │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↑ │
│ 第一层:底层原语(JVM/硬件级) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ CAS(CPU指令) | volatile(内存屏障) | LockSupport(阻塞原语) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

--- ## 第一层:底层原语 ### 1.1 CAS(Compare And Swap) **本质**:CPU 硬件指令(x86 的 CMPXCHG) **语义**:

if (内存值 == 预期值) {
内存值 = 新值
return true
} else {
return false
}

**作用**:无锁化编程的基石,实现原子性更新 **关联理解**: - CAS 是乐观锁思想的具体技术实现 - 数据库乐观锁(version 字段)是同一思想的另一种实现 ### 1.2 volatile **本质**:内存屏障(Memory Barrier) **保证**: - ✅ 可见性:一个线程修改,其他线程立即看到 - ✅ 有序性:禁止指令重排序 - ❌ 原子性:不保证! **⚠️ 重点理解**:

volatile int i = 0;
i++; // 这不是原子的!

// 因为 i++ 实际是三步:
// 1. 读取 i 的值
// 2. 计算 i + 1
// 3. 写回 i
// 步骤 1 和 3 之间可能被其他线程打断

### 1.3 LockSupport **本质**:线程阻塞/唤醒的底层机制(每个线程一个许可证) **核心方法**: - `park()` = 消耗许可证,没有则阻塞 - `unpark(thread)` = 发放许可证,如果线程在阻塞则唤醒 **vs wait/notify**: | 特性 | wait/notify | LockSupport | |------|-------------|-------------| | 是否需要锁 | 必须在 synchronized 中 | 不需要 | | 调用顺序 | wait 必须先于 notify | unpark 可先于 park | | 唤醒目标 | 随机或全部 | 精确指定线程 | **⚠️ 重点理解**: - LockSupport 和 wait/notify 是两套独立机制,不是封装关系 - 都最终调用操作系统的线程挂起/唤醒 - AQS 选择 LockSupport 因为可以精确唤醒队列中的下一个线程 ### 1.4 三者配合

CAS → 解决"如何无锁地原子更新状态"
volatile → 解决"如何让多线程看到最新状态"
LockSupport → 解决"获取不到时如何等待,释放时如何唤醒"

三者组合 → 构建 AQS

### 1.5 相关联想:进程与线程 **概念澄清**: - 进程 = 资源分配的基本单位(JVM 就是一个进程) - 线程 = CPU 调度的基本单位(JUC 操作的是线程) **CPU 与线程的关系**: - 某一瞬间:一个核心执行一个线程(一对一) - 宏观上:通过时间片轮转,多核服务多线程(多对多) **大核 vs 小核**: - 本质一样,区别是性能和功耗 - 由操作系统调度器决定分配,应用程序无法直接控制 --- ## 第二层:AQS(AbstractQueuedSynchronizer) ### 2.1 AQS 解决的问题 Doug Lea 发现各种同步器有大量重复逻辑,提取出 AQS:

ReentrantLock、Semaphore、CountDownLatch 的共同点:

  • 都需要一个状态
  • 获取失败都要排队
  • 排队时都要阻塞
  • 释放时都要唤醒

↓ 提取共同点 ↓

AQS:

  • 一个状态(state)
  • 一个等待队列(CLH)
  • 阻塞/唤醒机制
  • 获取/释放的流程控制
**这就是 DRY 原则的典范** ### 2.2 AQS 核心组成

AQS
├── state(volatile int)
│ └── 用 CAS 修改,含义由子类定义

└── CLH 队列(双向链表)
└── 存放等待的线程,FIFO 顺序

### 2.3 模板方法模式

┌─────────────────────────────────────────────────────────────────────┐
│ AQS │
├─────────────────────────────────────────────────────────────────────┤
│ 框架提供(固定流程): │
│ - acquire():tryAcquire → 失败则入队 → 阻塞 → 被唤醒重试 │
│ - release():tryRelease → 成功则唤醒队首 │
│ - 队列管理、阻塞/唤醒 │
├─────────────────────────────────────────────────────────────────────┤
│ 子类实现(定义语义): │
│ - tryAcquire():什么条件算"获取成功" │
│ - tryRelease():什么条件算"释放成功" │
└─────────────────────────────────────────────────────────────────────┘

### 2.4 独占模式 vs 共享模式 **⚠️ 重点理解**: 区别不是"能有几个线程获取",而是"有没有所有者": | 模式 | 核心特征 | 代表 | |------|----------|------| | 独占 | 有主人,谁加锁谁解锁,支持重入 | ReentrantLock、写锁 | | 共享 | 无主人,获取和释放可以是不同线程 | Semaphore、CountDownLatch、读锁 |

Semaphore(1) 效果上像独占,但:

  • 线程 A acquire,线程 B 可以 release
  • 甚至可以凭空 release(增加许可)

ReentrantLock:

  • 线程 A lock,线程 B unlock → 抛异常!
  • 必须是同一个线程
### 2.5 AQS 总结

AQS 本质 = 抽象队列同步器
= 一个 state + 一个双向队列
= 利用 CAS 保证原子性
= 利用 volatile 保证可见性
= 利用 LockSupport 控制线程阻塞/唤醒
= 模板方法让子类定义语义

--- ## 第三层:基于 AQS 的同步器 ### 3.1 ReentrantLock(可重入锁) **state 含义**:0 = 无锁,>0 = 重入次数 **核心特性**: - 可重入:同一线程多次获取,state + 1 - 有所有者:记录持有锁的线程 - 公平/非公平可选 **公平 vs 非公平**:

非公平(默认):新线程来了先尝试 CAS 抢锁,失败才排队
公平:新线程来了先看队列有没有人,有人就乖乖排队

非公平性能更好:减少线程切换开销

**⚠️ 为什么需要可重入**: ```java methodA() { lock.lock(); methodB(); // B 里也要加锁 lock.unlock(); } methodB() { lock.lock(); // 如果不可重入,这里会死锁! lock.unlock(); }

3.2 Semaphore(信号量)

state 含义:剩余许可数

工作方式

  • acquire():state > 0 则 state - 1,否则等待
  • release():state + 1,唤醒等待者

典型场景:限流、资源池

3.3 CountDownLatch(倒计时门闩)

state 含义:剩余计数

工作方式

  • countDown():state - 1,到 0 时唤醒所有等待者
  • await():state > 0 则阻塞,= 0 则通过

特点:一次性,用完不能重置

3.4 CyclicBarrier(循环屏障)

注意:不是直接基于 AQS,而是基于 ReentrantLock + Condition

vs CountDownLatch

CountDownLatchCyclicBarrier
一次性可重用
countDown 和 await 分离所有人都调用 await
一方等多方多方互相等待

3.5 ReentrantReadWriteLock(读写锁)

state 含义:一个 int 拆成两部分

  • 高 16 位 = 读锁持有线程数
  • 低 16 位 = 写锁重入次数

⚠️ 重点理解

为什么读锁要计数?

场景:3 个线程同时读 线程 A 获取读锁 → 读锁数 = 1 线程 B 获取读锁 → 读锁数 = 2 线程 C 获取读锁 → 读锁数 = 3 线程 A 释放读锁 → 读锁数 = 2(B、C 还在读) 线程 B 释放读锁 → 读锁数 = 1(C 还在读) 线程 C 释放读锁 → 读锁数 = 0(没人读了,写锁可以进来) 如果只用 0/1: 线程 A 释放 → 读锁 = 0,写锁进来,破坏 B、C 正在读的数据!

读锁计数的作用:防止写锁在读的过程中插入(读写冲突)

互斥规则

  • 读读并行
  • 读写互斥
  • 写写互斥

与数据库读写锁的关系

  • 思想层面:一样的
  • 实现层面:数据库更复杂(多粒度、意向锁、MVCC、死锁检测)

3.6 同步器对比

同步器模式可重用典型场景
ReentrantLock独占替代 synchronized
Semaphore共享限流、资源池
CountDownLatch共享等待 N 个任务完成
CyclicBarrier条件变量多阶段并行计算
ReadWriteLock独占+共享读多写少场景

第四层:应用层

4.1 并发集合设计思想演进

思路 1:全表锁(Hashtable) → 每个方法加 synchronized,并发度 = 1,太慢 思路 2:分段锁(JDK 1.7 ConcurrentHashMap) → 分成 16 段,每段一把锁,并发度 = 16 思路 3:CAS + 细粒度锁(JDK 1.8 ConcurrentHashMap) → 空桶 CAS 插入,非空桶 synchronized 锁头节点 思路 4:写时复制(CopyOnWriteArrayList) → 读无锁,写时复制整个数组

4.2 ConcurrentHashMap(JDK 1.8)

结构:数组 + 链表 + 红黑树

并发控制

场景操作方式锁粒度
桶为空CAS 插入无锁
桶非空synchronized(头节点)锁单个桶
扩容多线程协助分段处理

为什么不用 ReentrantLock

  • synchronized 在 JDK 1.6 后优化很多
  • 不需要手动释放
  • 内存占用更小

4.3 CopyOnWriteArrayList

核心思想:写时复制

读操作:直接读取数组,不加锁 写操作: 1. 加锁(ReentrantLock) 2. 复制原数组 3. 在新数组上修改 4. 把引用指向新数组 5. 释放锁

优缺点

  • ✅ 读无锁,性能高
  • ✅ 读写不阻塞
  • ❌ 写要复制,内存开销大
  • ❌ 弱一致性(可能读到旧数据)

适用场景:读多写少(配置列表、监听器列表、黑名单)

4.4 BlockingQueue

核心思想:生产者-消费者模式

实现原理(ArrayBlockingQueue):

ReentrantLock + 两个 Condition put(): while (队列满) notFull.await() 插入元素 notEmpty.signal() take(): while (队列空) notEmpty.await() 取出元素 notFull.signal()

常见实现

实现特点
ArrayBlockingQueue数组,有界,一把锁
LinkedBlockingQueue链表,可选有界,两把锁
SynchronousQueue不存储,直接传递
PriorityBlockingQueue优先级队列
DelayQueue延迟队列

4.5 ThreadPoolExecutor

为什么需要线程池

  • 每次 new Thread() 开销大
  • 线程数不可控
  • 线程用完就销毁,浪费

核心参数

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:非核心线程空闲存活时间
  • workQueue:任务队列(BlockingQueue)
  • threadFactory:线程工厂
  • handler:拒绝策略

执行流程

提交任务 ↓ 核心线程有空闲? → 是 → 核心线程执行 ↓ 否 队列未满? → 是 → 放入队列等待 ↓ 否 线程数 < 最大? → 是 → 创建非核心线程执行 ↓ 否 执行拒绝策略

拒绝策略

策略行为
AbortPolicy抛异常(默认)
CallerRunsPolicy调用者线程执行
DiscardPolicy静默丢弃
DiscardOldestPolicy丢弃队列最老的

4.6 ForkJoinPool

两大核心思想

1. 分治思想

大任务 ↓ fork ┌──────┴──────┐ 子任务1 子任务2 ↓ ↓ 结果1 结果2 └─────┬─────┘ ↓ join 最终结果

2. 工作窃取

每个线程有自己的双端队列 - 自己的任务从头部取(LIFO) - 偷别人的任务从尾部偷(FIFO) - 减少竞争,自动负载均衡

⚠️ 联想:Go 的 GMP 调度

工作窃取思想是跨语言的:

  • Java ForkJoinPool:线程闲了偷别人的任务
  • Go GMP:M 闲了偷别人 P 的 G

共同的设计哲学:本地优先 + 全局兜底

  1. 优先处理自己的任务(无锁,快)
  2. 自己没了,去偷别人的(有锁,频率低)
  3. 都没了,去全局队列找(兜底)

4.7 CompletableFuture

解决的问题:复杂的异步任务编排

核心能力

  • 创建异步任务:supplyAsync() / runAsync()
  • 链式处理:thenApply() / thenAccept() / thenRun()
  • 组合任务:allOf() / anyOf() / thenCombine()
  • 异常处理:exceptionally() / handle()

vs CountDownLatch

以前: CountDownLatch latch = new CountDownLatch(3); // 手动管理... 现在: CompletableFuture.allOf(task1, task2, task3) .thenAccept(result -> 处理); 更简洁、更直观

本质:下层同步器的集大成者,封装了 CountDownLatch、Semaphore 等的复杂性

4.8 第四层总结

线程池 = 装线程的集合,管理、复用、调度 ForkJoinPool = 分治思想 + 工作窃取 CompletableFuture = 集大成者 → 封装底层同步器 → 链式调用 → 不用手动写 CountDownLatch、Semaphore 等

JUC 核心设计思想

1. 分层抽象

底层原语 → AQS 框架 → 具体同步器 → 应用组件 每层只关心自己的职责,向上提供抽象

2. DRY(Don’t Repeat Yourself)

AQS 提取了所有同步器的共同逻辑,子类只需定义语义

3. 模板方法模式

AQS 定义流程骨架,子类填空

4. 锁粒度细化

全表锁 → 分段锁 → 节点锁 → 无锁(CAS)

5. 空间换时间

  • CopyOnWrite:复制数组换取读无锁
  • LongAdder:多个 Cell 换取减少竞争
  • ThreadLocal:每线程一份换取无竞争

6. 工作窃取

本地优先 + 闲时帮忙,自动负载均衡


易错点和重点标注

⚠️ volatile 不保证原子性

i++ 是三步操作,volatile 无法保证原子性

⚠️ LockSupport 不是 wait/notify 的封装

两套独立机制,LockSupport 解决了 wait/notify 的痛点

⚠️ 独占 vs 共享的核心区别

不是"几个线程能获取",而是"有没有所有者"

⚠️ 读写锁为什么读锁要计数

防止写锁在读的过程中插入,只有读锁数 = 0 写锁才能进来

⚠️ ForkJoinPool 的工作窃取

和 Go GMP 调度思想一致,是跨语言的设计智慧


整体关系图

┌─────────────────────────────────────────────────────────────────────────────┐ │ │ │ CompletableFuture / ThreadPoolExecutor / ConcurrentHashMap │ │ ↑ │ │ 依赖/组合 │ │ │ │ │ ReentrantLock / Semaphore / CountDownLatch / BlockingQueue │ │ ↑ │ │ 继承 │ │ │ │ │ AQS │ │ (state + CLH队列) │ │ ↑ │ │ 基于 │ │ │ │ │ CAS + volatile + LockSupport │ │ ↑ │ │ 依赖 │ │ │ │ │ Unsafe / JVM / CPU │ │ │ └─────────────────────────────────────────────────────────────────────────────┘

学习心得:好的设计思想是跨语言的,理解底层原理比记住 API 更重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/21 23:47:28

LobeChat可信计算环境搭建指南

LobeChat可信计算环境搭建指南 在企业对数据隐私和系统可控性要求日益严苛的今天&#xff0c;一个常见的困境浮出水面&#xff1a;我们想要用上最先进的大语言模型能力&#xff0c;却又不愿将敏感对话、内部知识甚至客户信息上传到第三方云平台。这种矛盾催生了一个明确需求——…

作者头像 李华
网站建设 2026/5/24 2:28:18

LobeChat技术债务清理计划

LobeChat技术债务清理计划 在大语言模型&#xff08;LLM&#xff09;迅速普及的今天&#xff0c;越来越多用户不再满足于“能对话”的基础体验&#xff0c;而是追求更安全、可定制、可持续演进的AI交互方式。尽管像ChatGPT这样的商业产品提供了出色的开箱即用体验&#xff0c;…

作者头像 李华
网站建设 2026/5/23 1:51:10

LobeChat数据库选型分析:SQLite vs PostgreSQL适用场景

LobeChat数据库选型分析&#xff1a;SQLite vs PostgreSQL适用场景 在构建现代AI聊天应用的今天&#xff0c;一个看似不起眼却至关重要的决策&#xff0c;往往决定了整个系统的生命力——数据库选型。LobeChat 作为一款功能丰富的开源大模型交互界面&#xff0c;支持多模型接入…

作者头像 李华
网站建设 2026/5/22 4:00:32

Chrome网页文本批量替换插件:高效内容编辑的终极解决方案

Chrome网页文本批量替换插件&#xff1a;高效内容编辑的终极解决方案 【免费下载链接】chrome-extensions-searchReplace 项目地址: https://gitcode.com/gh_mirrors/ch/chrome-extensions-searchReplace 在日常网页浏览和内容编辑工作中&#xff0c;你是否曾遇到过这样…

作者头像 李华
网站建设 2026/5/16 6:31:40

显卡驱动清理终极指南:告别卡顿的3个秘密武器

显卡驱动清理终极指南&#xff1a;告别卡顿的3个秘密武器 【免费下载链接】display-drivers-uninstaller Display Driver Uninstaller (DDU) a driver removal utility / cleaner utility 项目地址: https://gitcode.com/gh_mirrors/di/display-drivers-uninstaller 还在…

作者头像 李华