Java并发核心机制详解:线程池、CAS、AQS、锁升级
1. 线程池(ThreadPool)
1.1 为什么需要线程池
问题场景:
- 频繁创建和销毁线程带来巨大开销
- 无限制创建线程导致内存溢出
- 缺乏统一管理,难以监控和调优
线程池的优势:
- 降低资源消耗:复用线程,减少创建/销毁开销
- 提高响应速度:任务到达时无需等待线程创建
- 提高可管理性:统一管理、监控、调优
- 提高可扩展性:支持动态调整线程数量
1.2 线程池核心参数
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
1.3 线程池工作流程
提交任务
↓
┌─────────────────────────────────────────────────────────┐
│ 1. 核心线程数未满? │
│ ├── 是 → 创建核心线程执行任务 │
│ └── 否 → 继续 │
├─────────────────────────────────────────────────────────┤
│ 2. 工作队列未满? │
│ ├── 是 → 任务加入队列等待 │
│ └── 否 → 继续 │
├─────────────────────────────────────────────────────────┤
│ 3. 线程数未达最大值? │
│ ├── 是 → 创建非核心线程执行任务 │
│ └── 否 → 执行拒绝策略 │
└─────────────────────────────────────────────────────────┘
1.4 四种拒绝策略
| 拒绝策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy(默认) | 抛出RejectedExecutionException | 不允许丢失任务 |
| CallerRunsPolicy | 由提交任务的线程执行 | 允许降级执行 |
| DiscardPolicy | 静默丢弃任务 | 可容忍丢失 |
| DiscardOldestPolicy | 丢弃队列中最老的任务 | 优先执行新任务 |
java
// 自定义拒绝策略
public class CustomRejectionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录日志、降级处理等
log.warn("任务被拒绝: " + r.toString());
}
}
1.5 常见线程池类型
1.5.1 FixedThreadPool(固定数量线程池)
java
ExecutorService pool = Executors.newFixedThreadPool(10);
- 核心线程数 = 最大线程数 = 固定值
- 使用LinkedBlockingQueue(无界队列)
- 风险:任务堆积导致OOM
1.5.2 SingleThreadExecutor(单线程池)
java
ExecutorService pool = Executors.newSingleThreadExecutor();
- 核心线程数 = 最大线程数 = 1
- 保证任务按顺序执行
- 风险:任务堆积导致OOM
1.5.3 CachedThreadPool(缓存线程池)
java
ExecutorService pool = Executors.newCachedThreadPool();
- 核心线程数 = 0,最大线程数 = Integer.MAX_VALUE
- 使用SynchronousQueue(不存储元素)
- 风险:创建大量线程导致OOM
1.5.4 ScheduledThreadPool(定时线程池)
java
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
- 支持定时和周期性任务
- 使用DelayedWorkQueue
1.6 线程池参数设置经验
| 参数 | 推荐值 | 说明 |
|---|---|---|
| CPU密集型任务 | N+1(N为CPU核数) | 减少上下文切换 |
| IO密集型任务 | 2N 或 N/(1-阻塞系数) | 充分利用IO等待时间 |
| 混合型任务 | 根据压测调整 | 可拆分为CPU和IO子任务 |
动态线程池实现:
java
// 动态调整核心线程数和最大线程数
public class DynamicThreadPool {
private final ThreadPoolExecutor executor;
public void updateCorePoolSize(int coreSize) {
executor.setCorePoolSize(coreSize);
}
public void updateMaxPoolSize(int maxSize) {
executor.setMaximumPoolSize(maxSize);
}
}
2. CAS(Compare And Swap)
2.1 CAS基本概念
CAS是一种乐观锁机制,包含三个操作数:
- V(内存值):要更新的变量
- E(期望值):期望的旧值
- N(新值):要设置的新值
操作语义:当且仅当V == E时,将V的值设置为N,否则不做任何操作。
2.2 CAS底层实现
java
// AtomicInteger的incrementAndGet方法
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// Unsafe类的getAndAddInt方法
public final int getAndAddInt(Object obj, long offset, int delta) {
int expected;
do {
expected = this.getIntVolatile(obj, offset); // 获取当前值
} while (!this.compareAndSwapInt(obj, offset,
expected,
expected + delta)); // CAS更新
return expected;
}
2.3 CAS的三大问题
2.3.1 ABA问题
问题描述:值从A变为B再变回A,CAS会误认为没有变化。
线程1:读取V=A
线程2:V=A → V=B
线程3:V=B → V=A
线程1:CAS更新(期望A,实际A,但中间经历过变化)
解决方案:
java
// 使用AtomicStampedReference(版本号)
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(1, 0);
int stamp = ref.getStamp(); // 获取版本号
ref.compareAndSet(1, 2, stamp, stamp + 1);
// 使用AtomicMarkableReference(布尔标记)
AtomicMarkableReference<Integer> ref =
new AtomicMarkableReference<>(1, false);
2.3.2 自旋开销问题
问题描述:CAS失败后会一直自旋重试,在高并发场景下消耗大量CPU。
解决方案:
- 使用LongAdder代替AtomicLong(分段CAS)
- 减小CAS粒度
- 使用锁代替CAS
2.3.3 只能保证一个变量的原子操作
问题描述:CAS一次只能操作一个变量。
解决方案:
java
// 使用AtomicReference封装多个变量
class Pair {
int x, y;
// ...
}
AtomicReference<Pair> ref = new AtomicReference<>(new Pair(0, 0));
// 使用锁保证多变量原子性
synchronized(lock) {
// 同时更新多个变量
}
2.4 CAS应用场景
| 场景 | 类 | 说明 |
|---|---|---|
| 原子计数器 | AtomicInteger、AtomicLong | 高并发计数 |
| 无锁队列 | ConcurrentLinkedQueue | 高并发队列 |
| 无锁栈 | ConcurrentLinkedStack | 高并发栈 |
| 比较交换 | AtomicReference | 引用类型原子操作 |
3. AQS(AbstractQueuedSynchronizer)
3.1 AQS核心概念
AQS是Java并发包的基石,ReentrantLock、Semaphore、CountDownLatch等都是基于AQS实现。
核心思想:
- 维护一个
volatile int state表示同步状态 - 通过CLH(Craig, Landin, and Hagersten)队列管理等待线程
3.2 AQS工作原理
┌─────────────────────────────────────────────────────────┐
│ AQS核心结构 │
├─────────────────────────────────────────────────────────┤
│ volatile int state; // 同步状态 │
│ └── 0:未锁定 │
│ └── >0:已锁定(可重入次数) │
├─────────────────────────────────────────────────────────┤
│ CLH双向队列 │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ head │→ │ Node │→ │ Node │→ ... │
│ └──────┘ └──────┘ └──────┘ │
│ 代表持有锁的线程 等待获取锁的线程 │
└─────────────────────────────────────────────────────────┘
3.3 独占锁获取流程
java
// acquire方法(以ReentrantLock为例)
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 入队等待
selfInterrupt();
}
// tryAcquire实现(非公平锁)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 锁未被占用
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 可重入
setState(c + acquires);
return true;
}
return false;
}
3.4 公平锁 vs 非公平锁
| 特性 | 公平锁 | 非公平锁 |
|---|---|---|
| 加锁顺序 | 按照请求顺序 | 允许插队 |
| 实现方式 | hasQueuedPredecessors() | 无额外检查 |
| 吞吐量 | 较低 | 较高 |
| 适用场景 | 需要公平性 | 性能优先 |
java
// 公平锁实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平锁会检查队列中是否有等待线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ...
}
3.5 共享锁获取流程
java
// acquireShared方法(以CountDownLatch为例)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 尝试获取共享锁
doAcquireShared(arg); // 入队等待
}
// CountDownLatch的tryAcquireShared实现
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
// state为0时返回1(成功),否则返回-1(失败)
}
3.6 Condition条件队列
java
// ReentrantLock的Condition
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
// 生产者
lock.lock();
try {
while (queue.isFull()) {
notFull.await(); // 进入条件队列等待
}
// 生产数据
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
3.7 AQS核心方法
| 方法 | 说明 | 独占/共享 |
|---|---|---|
| tryAcquire(arg) | 尝试获取独占锁 | 独占 |
| tryRelease(arg) | 尝试释放独占锁 | 独占 |
| tryAcquireShared(arg) | 尝试获取共享锁 | 共享 |
| tryReleaseShared(arg) | 尝试释放共享锁 | 共享 |
| isHeldExclusively() | 是否独占 | 独占 |
4. 锁升级机制
4.1 Java对象头
┌─────────────────────────────────────────────────────────┐
│ 64位JVM对象头 │
├─────────────────────────────────────────────────────────┤
│ Mark Word (8字节) │
│ ├── 无锁:hashcode(31) | age(4) | 偏向(1) = 0 │
│ ├── 偏向锁:threadId(54) | age(4) | 偏向(1) = 1 │
│ ├── 轻量级锁:指向栈中锁记录的指针(62) | 锁标志(2) │
│ ├── 重量级锁:指向Monitor的指针(62) | 锁标志(2) │
│ └── GC标记:空 │
├─────────────────────────────────────────────────────────┤
│ Klass Pointer (4字节) - 指向类元数据 │
│ + 4字节对齐填充 │
└─────────────────────────────────────────────────────────┘
4.2 锁升级流程
┌─────────────────────────────────────────────────────────┐
│ 锁升级流程 │
├─────────────────────────────────────────────────────────┤
│ 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 │
│ ↑ ↑ ↑ ↑ │
│ 初始状态 首次加锁 竞争出现 竞争激烈 │
└─────────────────────────────────────────────────────────┘
4.3 偏向锁
4.3.1 偏向锁原理
- 只有一个线程访问同步块时启用
- 在对象头Mark Word中记录线程ID
- 下次同一线程访问时无需任何同步操作
4.3.2 偏向锁获取流程
1. 检查对象头Mark Word是否存储当前线程ID
├── 是 → 直接进入同步块(无需CAS)
└── 否 → 继续
2. CAS将线程ID设置到Mark Word
├── 成功 → 获取偏向锁
└── 失败 → 撤销偏向锁,升级为轻量级锁
4.3.3 偏向锁撤销
- 批量重偏向:当撤销次数超过阈值(默认20),将新线程ID偏向
- 批量撤销:当撤销次数超过阈值(默认40),所有对象变成无锁状态
4.4 轻量级锁
4.4.1 轻量级锁原理
- 多个线程交替访问同步块时启用
- 通过CAS操作Mark Word实现
- 不阻塞线程,自旋重试
4.4.2 轻量级锁加锁流程
1. 在线程栈帧中创建锁记录(Lock Record)
2. CAS将Mark Word替换为指向锁记录的指针
├── 成功 → 获取轻量级锁
└── 失败 → 自旋重试
3. 自旋失败超过阈值 → 升级为重量级锁
4.4.3 自旋优化
- 自适应自旋:根据上次自旋结果动态调整自旋次数
- 锁消除:JIT编译器消除不可能存在竞争的锁
- 锁粗化:将多个连续的同步块合并为一个
4.5 重量级锁
4.5.1 重量级锁原理
- 多个线程同时访问同步块时启用
- 依赖操作系统Mutex Lock实现
- 线程会被阻塞和唤醒,涉及内核态/用户态切换
4.5.2 Monitor结构
┌─────────────────────────────────────────────────────────┐
│ Monitor对象 │
├─────────────────────────────────────────────────────────┤
│ Owner → 持有锁的线程 │
│ EntryList → 等待获取锁的线程队列 │
│ WaitSet → 调用wait()等待的线程 │
│ recursions → 锁重入次数 │
└─────────────────────────────────────────────────────────┘
4.6 锁升级总结
| 锁状态 | 存储内容 | 适用场景 | 性能开销 |
|---|---|---|---|
| 偏向锁 | 线程ID | 单线程访问 | 最低 |
| 轻量级锁 | 栈帧锁记录指针 | 线程交替访问 | 较低 |
| 重量级锁 | Monitor指针 | 多线程同时访问 | 最高 |
4.7 锁消除与锁粗化
4.7.1 锁消除
JIT编译器通过逃逸分析,消除不可能存在竞争的锁。
java
// 锁消除前
public String concat(String s1, String s2) {
StringBuffer sb = new StringBuffer(); // 每次创建新对象
sb.append(s1);
sb.append(s2);
return sb.toString();
}
// 锁消除后:StringBuffer的锁被消除
// 因为sb是局部变量,不可能逃逸出方法
4.7.2 锁粗化
将多个连续的同步块合并为一个,减少加锁/解锁次数。
java
// 锁粗化前
for (int i = 0; i < 100; i++) {
synchronized (lock) {
// 每次循环都加锁/解锁
}
}
// 锁粗化后
synchronized (lock) {
for (int i = 0; i < 100; i++) {
// 只加锁一次
}
}
5. 企业级面试题
5.1 线程池相关
Q1:线程池的核心参数有哪些?各自的作用是什么?
A:核心参数有7个:
corePoolSize:核心线程数,即使空闲也会保留maximumPoolSize:最大线程数keepAliveTime:空闲线程最大存活时间unit:时间单位workQueue:任务队列,存放等待执行的任务threadFactory:线程工厂,创建线程handler:拒绝策略,队列满且线程数达最大值时的处理方式
Q2:线程池的任务执行流程是什么?
A:
- 提交任务时,如果核心线程数未满,创建核心线程执行
- 如果核心线程数已满,将任务加入工作队列
- 如果工作队列已满,检查线程数是否达到最大值
- 如果未达到最大值,创建非核心线程执行
- 如果达到最大值,执行拒绝策略
Q3:为什么不推荐使用Executors创建线程池?
A:因为Executors创建的线程池存在OOM风险:
FixedThreadPool和SingleThreadExecutor使用无界LinkedBlockingQueue,任务堆积会OOMCachedThreadPool使用SynchronousQueue,最大线程数为Integer.MAX_VALUE,可能创建大量线程OOM- 建议使用ThreadPoolExecutor手动创建,明确参数
Q4:如何合理配置线程池参数?
A:根据任务类型配置:
- CPU密集型:核心线程数 = CPU核数 + 1
- IO密集型:核心线程数 = 2 × CPU核数 或 CPU核数 / (1 - 阻塞系数)
- 混合型:通过压测调整,或拆分为CPU和IO子任务
实际项目中建议通过配置中心动态调整,如美团的动态线程池方案。
Q5:线程池的拒绝策略有哪些?如何自定义?
A:四种内置策略:
AbortPolicy:抛出RejectedExecutionException(默认)CallerRunsPolicy:由提交任务的线程执行DiscardPolicy:静默丢弃任务DiscardOldestPolicy:丢弃队列中最老的任务
自定义需要实现RejectedExecutionHandler接口,重写rejectedExecution方法。
Q6:线程池如何实现优雅关闭?
A:使用shutdown()和awaitTermination():
java
executor.shutdown(); // 停止接收新任务,等待已提交任务完成
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 超时强制关闭
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("线程池未完全关闭");
}
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
5.2 CAS相关
Q7:什么是CAS?它的底层实现原理是什么?
A:CAS(Compare And Swap)是一种乐观锁机制,包含三个操作数:内存值V、期望值E、新值N。
底层通过Unsafe类的native方法实现,使用CPU的原子指令(如x86的CMPXCHG)保证比较和交换的原子性。
Q8:CAS存在哪些问题?如何解决?
A:三大问题及解决方案:
- ABA问题:使用AtomicStampedReference(版本号)或AtomicMarkableReference(布尔标记)
- 自旋开销:使用LongAdder代替AtomicLong(分段CAS)
- 单变量限制:使用AtomicReference封装多个变量,或使用锁
Q9:AtomicLong和LongAdder有什么区别?
A:
AtomicLong:基于CAS,高并发时自旋竞争激烈LongAdder:分段CAS,将单点更新分散到多个单元- LongAdder在高并发下性能更好,但不保证实时一致性
- 适合统计场景,不适合需要精确值的场景
Q10:CAS和synchronized有什么区别?
A:
| 特性 | CAS | synchronized |
|---|---|---|
| 实现方式 | 乐观锁 | 悲观锁 |
| 是否阻塞 | 否,自旋重试 | 是,线程阻塞 |
| 适用场景 | 低竞争 | 高竞争 |
| 性能 | 低竞争时好 | 高竞争时好 |
| 保证 | 原子性 | 原子性、可见性、有序性 |
5.3 AQS相关
Q11:什么是AQS?它的核心原理是什么?
A:AQS(AbstractQueuedSynchronizer)是Java并发包的基石,ReentrantLock、Semaphore、CountDownLatch等都是基于AQS实现。
核心原理:
- 维护volatile int state表示同步状态
- 通过CLH双向队列管理等待线程
- 子类重写tryAcquire/tryRelease实现独占锁
- 子类重写tryAcquireShared/tryReleaseShared实现共享锁
Q12:ReentrantLock是如何基于AQS实现的?
A:
state:表示锁的重入次数,0表示未锁定exclusiveOwnerThread:记录持有锁的线程- 加锁时CAS将state从0改为1,重入时state+1
- 解锁时state-1,直到state=0释放锁
- 公平锁通过hasQueuedPredecessors()检查队列中是否有等待线程
Q13:CountDownLatch和CyclicDownLatch有什么区别?
A:
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 用途 | 一个/多个线程等待其他线程完成 | 多个线程互相等待到齐 |
| 重用 | 一次性 | 可重用(reset) |
| 实现 | AQS共享模式 | ReentrantLock + Condition |
| 场景 | 主线程等待子任务完成 | 多线程分阶段计算 |
Q14:Semaphore是如何实现许可控制的?
A:
- 初始化时设置state为许可数量
acquire():CAS将state减1,失败则阻塞release():CAS将state加1,唤醒等待线程- 公平模式使用FIFO队列,非公平模式允许插队
Q15:AQS中的CLH队列是什么?有什么特点?
A:CLH是一种双向链表队列,由Craig、Landin和Hagersten提出。
特点:
- 虚拟队列,通过head和tail指针管理
- 节点包含线程引用、等待状态、前驱/后继指针
- 入队通过CAS保证原子性
- 出队时通过自旋检查前驱节点状态
- 支持中断和超时
5.4 锁升级相关
Q16:Java中的锁有哪几种状态?
A:四种状态(从低到高):
- 无锁:没有线程访问同步块
- 偏向锁:只有一个线程访问
- 轻量级锁:多个线程交替访问(无竞争)
- 重量级锁:多个线程同时访问(有竞争)
锁只能升级不能降级(偏向锁除外)。
Q17:偏向锁的原理是什么?什么情况下会撤销?
A:偏向锁原理:
- 在对象头Mark Word中记录线程ID
- 同一线程再次访问时无需CAS,直接比较线程ID
- 只有一个线程访问时性能最优
撤销条件:
- 撤销次数超过阈值(批量重偏向/批量撤销)
- 其他线程尝试获取偏向锁
- 调用对象的hashcode()方法(会撤销偏向状态)
Q18:轻量级锁的加锁过程是什么?
A:
- 在线程栈帧中创建锁记录(Lock Record)
- CAS将对象头Mark Word替换为指向锁记录的指针
- 成功则获取轻量级锁
- 失败则自旋重试
- 自旋超过阈值,升级为重量级锁
Q19:什么情况下会触发锁升级?
A:
- 偏向锁 → 轻量级锁:其他线程尝试获取偏向锁时
- 轻量级锁 → 重量级锁:自旋超过阈值或竞争激烈时
- 无锁 → 轻量级锁:直接发生竞争时跳过偏向锁
Q20:锁消除和锁粗化是什么?有什么作用?
A:
-
锁消除:JIT编译器通过逃逸分析,消除不可能存在竞争的锁
- 如StringBuffer.append()的锁被消除
- 作用:减少不必要的同步开销
-
锁粗化:将多个连续的同步块合并为一个
- 如for循环中的同步块合并
- 作用:减少加锁/解锁次数
5.5 综合实战题
Q21:如何设计一个高并发的计数器?
A:根据场景选择方案:
- 高并发读、低写入:使用LongAdder(分段CAS)
- 需要精确值:使用AtomicLong(CAS)
- 需要多变量原子操作:使用AtomicReference或锁
- 分布式环境:使用Redis INCR或数据库乐观锁
Q22:线上服务CPU飙高,如何排查是否与锁有关?
A:
- 使用
top找到CPU高的进程PID - 使用
top -Hp PID找到CPU高的线程TID - 使用
jstack PID导出线程堆栈 - 检查是否有大量线程在等待锁(BLOCKED/WAITING状态)
- 分析锁竞争热点,优化锁粒度或使用无锁方案
Q23:如何避免死锁?
A:
- 固定加锁顺序:所有线程按相同顺序获取锁
- 设置超时时间:使用tryLock(timeout)避免无限等待
- 避免嵌套锁:减少锁的持有时间
- 使用无锁方案:CAS、ConcurrentHashMap等
- 死锁检测:使用jstack或ThreadMXBean检测
Q24:ConcurrentHashMap是如何实现高并发的?
A:JDK 8实现:
- 数据结构:数组 + 链表 + 红黑树
- 分段锁:每个桶独立加锁,锁粒度为Node
- CAS操作:插入空桶时使用CAS
- synchronized:链表/红黑树操作使用synchronized(锁升级优化)
- size():使用baseCount + CounterCell数组分散计数
Q25:AQS在实际项目中有哪些应用?
A:
- ReentrantLock:可重入互斥锁,替代synchronized
- ReadWriteLock:读写锁,提高读多写少场景性能
- Semaphore:限流、资源控制
- CountDownLatch:多线程同步、等待任务完成
- CyclicBarrier:多线程分阶段计算
- Phaser:灵活的分阶段同步(JDK 7+)
6. 总结
6.1 核心要点速查
| 机制 | 核心原理 | 关键点 |
|---|---|---|
| 线程池 | 复用线程执行任务 | 7个核心参数、执行流程、拒绝策略 |
| CAS | 乐观锁,原子比较交换 | ABA问题、自旋开销、Unsafe实现 |
| AQS | state + CLH队列 | 独占/共享模式、公平/非公平锁 |
| 偏向锁 | 记录线程ID | 单线程优化、批量重偏向 |
| 轻量级锁 | CAS + 自旋 | 交替访问、自适应自旋 |
| 重量级锁 | Monitor + 阻塞 | 高竞争场景、性能开销大 |
6.2 面试答题技巧
- 原理先行:先说核心原理,再展开细节
- 对比分析:用表格对比相似概念
- 结合场景:说明适用场景和选择依据
- 举例说明:用代码或图示辅助解释
- 关注细节:如AQS的state含义、锁的存储结构
6.3 技术选型建议
| 场景 | 推荐方案 |
|---|---|
| 高并发计数 | LongAdder |
| 限流控制 | Semaphore |
| 任务编排 | CountDownLatch/CyclicBarrier |
| 读写分离 | ReadWriteLock |
| 锁升级优化 | 偏向锁 → 轻量级锁 |