一、AQS 抽象队列同步器(超级详细知识点)
1. AQS 是什么
- AQS 是 JUC 里所有锁和同步工具的底层基础框架。
- ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier 全部基于 AQS 实现。
- 采用 模板方法模式:流程固定,子类只实现抢锁 / 释放逻辑。
2. AQS 核心三大组成
-
state 变量(volatile int)
- 0 = 无锁
-
0 = 已加锁(数值代表重入次数)
- volatile 保证多线程之间可见性
-
CLH 变体双向队列
- 存放抢锁失败、进入等待的线程
- 每个节点包含:线程、等待状态、前驱 / 后继指针
- 先进先出(FIFO),保证公平性
-
独占 / 共享两种模式
- 独占模式:同一时间只有一个线程能持有锁(ReentrantLock)
- 共享模式:多个线程可同时持有(CountDownLatch、Semaphore)
3. AQS 节点 5 种等待状态
- CANCELLED(1) :线程取消 / 超时 / 中断,不再参与竞争
- SIGNAL(-1) :当前节点释放锁后,需要唤醒后继节点
- CONDITION(-2) :线程在 Condition 队列中等待
- PROPAGATE(-3) :共享模式下,唤醒需要传播给后面节点
- 0:初始状态
4. 独占模式获取锁流程(完整逻辑)
-
线程调用 tryAcquire 尝试抢锁
-
抢锁成功:设置当前线程为锁持有者,直接执行业务
-
抢锁失败:
- 封装成 Node 节点,加入队列尾部
- 检查前驱节点状态,确保前驱是 SIGNAL
- 调用 LockSupport.park () 阻塞当前线程
-
被唤醒后:
- 再次尝试抢锁
- 成功则成为新头节点,失败继续阻塞
5. 独占模式释放锁流程
- 调用 tryRelease 尝试释放锁
- state 减到 0 才算真正释放
- 唤醒队列中头节点的后继节点
- 被唤醒线程重新竞争锁
6. 共享模式与独占模式的区别
- 独占:只有一个线程能成功
- 共享:可以同时有多个线程成功
- 共享模式释放后会连续唤醒后面的节点(传播唤醒)
二、ReentrantLock 详细知识点
1. ReentrantLock 是什么
- JDK 层面实现的 可重入独占锁
- 完全依靠 AQS 实现,比 synchronized 更灵活
2. 可重入性原理
- 同一个线程多次加锁,不会阻塞自己
- 每次加锁 state+1,解锁 state-1
- 只有 state 回到 0,锁才真正释放
3. 公平锁 vs 非公平锁(超级重点)
非公平锁(默认)
- 线程上来直接 CAS 抢锁,抢不到再排队
- 优点:吞吐量极高
- 缺点:可能产生线程饥饿
公平锁
- 先检查队列中是否有线程在等待
- 有等待 → 直接进队,绝不插队
- 优点:绝对公平,无饥饿
- 缺点:吞吐量低
核心区别:是否执行 hasQueuedPredecessors () 判断队列
4. ReentrantLock 常用方法的意义
- **lock()**阻塞获取锁,不响应中断,拿不到锁就一直等。
- **lockInterruptibly()**可中断获取锁,阻塞过程中能被中断,适合需要取消任务的场景。
- **tryLock()**尝试一次,立即返回 true/false,不阻塞。
- **tryLock(timeout)**在一定时间内尝试抢锁,超时返回 false。
- **unlock()**释放锁,必须放在 finally 里,否则极易死锁。
5. ReentrantLock 与 synchronized 对比
- synchronized:JVM 实现、自动加解锁、异常自动释放、功能简单
- ReentrantLock:JDK 实现、手动控制、支持中断 / 超时 / 公平锁 / 多 Condition
三、JUC 三大并发工具类(超详细知识点)
1. CountDownLatch 倒计时门闩
作用
让一个或多个线程 等待其他一批线程执行完 再继续。
原理
- 初始化一个计数值
- countDown ():计数 -1
- await ():阻塞直到计数 = 0
特点
- 不可重用,计数到 0 就失效
- 基于 AQS 共享模式
典型场景
- 主线程等待多个子线程初始化完成
- 分布式接口多任务并行聚合结果
2. CyclicBarrier 循环屏障
作用
一组线程互相等待,全部到齐后再一起执行。
原理
- 设定一组线程数量
- 每调用一次 await (),计数 -1
- 计数 = 0 时,唤醒所有等待线程,并自动重置计数
特点
- 可循环使用
- 基于 ReentrantLock + Condition 实现
- 支持屏障任务:所有线程到齐后优先执行一个任务
典型场景
- 多线程分批计算
- 游戏多玩家同时进入战斗
3. Semaphore 信号量
作用
控制同时执行的线程数量,用于限流、池化资源保护。
原理
- 初始化许可数量
- acquire ():获取一个许可,没有则阻塞
- release ():释放一个许可
特点
- 基于 AQS 共享模式
- 支持公平 / 非公平模式
典型场景
- 接口限流
- 数据库连接池控制
- 池化资源并发控制
四、阻塞队列 BlockingQueue(详细知识点)
1. 作用
- 实现 生产者 - 消费者模型
- 自动处理:队列满 → 阻塞写;队列空 → 阻塞读
- 内部自带锁与等待唤醒,无需手动写 wait/notify
2. 四类核心方法(必须背)
- 抛异常:add、remove、element
- 返回布尔 /null:offer、poll、peek
- 阻塞:put、take
- 超时:offer (timeout)、poll (timeout)
3. 四大常用实现类
ArrayBlockingQueue
- 数组实现,有界队列
- 必须指定容量
- 全局一把锁,读写共用同一把锁
LinkedBlockingQueue
- 链表实现
- 默认无界,可指定为有界
- 读写分离锁,并发性能更高
SynchronousQueue
- 不存储任何元素
- 每一个 put 必须等待一个 take
- 高并发直接传递任务,吞吐量极高
- Executors.newCachedThreadPool 底层使用
PriorityBlockingQueue
- 支持优先级排序
- 无界队列
- 按照元素自然排序或自定义比较器
4. 阻塞队列核心意义
- 解耦生产者与消费者
- 平衡生产速度与消费速度
- 自动流量削峰
- 线程池的核心依赖组件
五、本章面试高频深度题
- AQS 的 state、CLH 队列、节点状态分别有什么用?
- AQS 独占与共享模式的区别?
- ReentrantLock 可重入原理是什么?
- 公平锁与非公平锁的区别、优缺点、底层差异?
- ThreadLocal 为什么 key 用弱引用?不调用 remove 为什么泄漏?
- CountDownLatch 与 CyclicBarrier 区别?
- Semaphore 实现限流的原理?
- 阻塞队列四类方法区别?常用队列有哪些?