Java学习第五天:AQS + ReentrantLock + JUC 并发工具 + 阻塞队列

5 阅读5分钟

一、AQS 抽象队列同步器(超级详细知识点)

1. AQS 是什么

  • AQS 是 JUC 里所有锁和同步工具的底层基础框架
  • ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier 全部基于 AQS 实现。
  • 采用 模板方法模式:流程固定,子类只实现抢锁 / 释放逻辑。

2. AQS 核心三大组成

  1. state 变量(volatile int)

    • 0 = 无锁
    • 0 = 已加锁(数值代表重入次数)

    • volatile 保证多线程之间可见性
  2. CLH 变体双向队列

    • 存放抢锁失败、进入等待的线程
    • 每个节点包含:线程、等待状态、前驱 / 后继指针
    • 先进先出(FIFO),保证公平性
  3. 独占 / 共享两种模式

    • 独占模式:同一时间只有一个线程能持有锁(ReentrantLock)
    • 共享模式:多个线程可同时持有(CountDownLatch、Semaphore)

3. AQS 节点 5 种等待状态

  • CANCELLED(1) :线程取消 / 超时 / 中断,不再参与竞争
  • SIGNAL(-1) :当前节点释放锁后,需要唤醒后继节点
  • CONDITION(-2) :线程在 Condition 队列中等待
  • PROPAGATE(-3) :共享模式下,唤醒需要传播给后面节点
  • 0:初始状态

4. 独占模式获取锁流程(完整逻辑)

  1. 线程调用 tryAcquire 尝试抢锁

  2. 抢锁成功:设置当前线程为锁持有者,直接执行业务

  3. 抢锁失败:

    • 封装成 Node 节点,加入队列尾部
    • 检查前驱节点状态,确保前驱是 SIGNAL
    • 调用 LockSupport.park () 阻塞当前线程
  4. 被唤醒后:

    • 再次尝试抢锁
    • 成功则成为新头节点,失败继续阻塞

5. 独占模式释放锁流程

  1. 调用 tryRelease 尝试释放锁
  2. state 减到 0 才算真正释放
  3. 唤醒队列中头节点的后继节点
  4. 被唤醒线程重新竞争锁

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. 阻塞队列核心意义

  • 解耦生产者与消费者
  • 平衡生产速度与消费速度
  • 自动流量削峰
  • 线程池的核心依赖组件

五、本章面试高频深度题

  1. AQS 的 state、CLH 队列、节点状态分别有什么用?
  2. AQS 独占与共享模式的区别?
  3. ReentrantLock 可重入原理是什么?
  4. 公平锁与非公平锁的区别、优缺点、底层差异?
  5. ThreadLocal 为什么 key 用弱引用?不调用 remove 为什么泄漏?
  6. CountDownLatch 与 CyclicBarrier 区别?
  7. Semaphore 实现限流的原理?
  8. 阻塞队列四类方法区别?常用队列有哪些?