目录
- 1. 并发问题的本质:可见性、原子性、有序性
- 2. Java 内存模型(JMM)与 happens-before 规则
- 3. 互斥与同步的实现方式总览
- 4. synchronized 关键字
- 5. Lock 家族:ReentrantLock / ReadWriteLock / StampedLock
- 6. 条件队列与线程协作:wait/notify、Condition、LockSupport
- 7. AQS 同步器与并发工具:Semaphore / CountDownLatch / CyclicBarrier / Phaser / Exchanger
- 8. 原子类与 CAS:AtomicXXX、LongAdder/LongAccumulator、ABA 问题
- 9. 并发容器与阻塞队列
- 10. ThreadLocal 的使用与陷阱
- 11. 线程池与任务调度:ThreadPoolExecutor / ScheduledThreadPoolExecutor / ForkJoinPool / CompletableFuture
- 12. 无锁/低锁思路与性能优化
- 13. 典型并发 Bug 与排错方法
- 14. 选型与实践清单(速查表)
- 15. 面试要点与手撕小题
1. 并发问题的本质:可见性、原子性、有序性
- 可见性:一个线程对共享变量的写,何时对另一个线程可见?(CPU 缓存、多级缓存)
- 原子性:复合操作是否不可中断?如
count++非原子。 - 有序性:编译器/CPU 指令重排可能改变执行顺序。
解决思路:利用 JMM 的 happens-before 关系建立“先行发生”,或使用锁/原子操作/
volatile保证。
2. Java 内存模型(JMM)与 happens-before 规则
常见规则(高频考点):
- 程序次序规则:单线程内,按程序顺序。
- 监视器锁规则:对同一锁的
unlock先行发生于随后对该锁的lock。 - volatile 规则:对某个
volatile变量的写,先行发生于后续读。 - 线程启动:
Thread.start()先行发生于新线程中的任何操作。 - 线程终止:线程中的所有操作先行发生于
Thread.join()成功返回。 - 中断规则:对线程
interrupt()先行发生于被中断线程检测到中断事件。 - 对象发布与最终字段:构造函数中对
final字段的写,对其他线程是安全可见的(对象正确发布)。
3. 互斥与同步的实现方式总览
按粒度分层理解:
- 语言级:
synchronized、volatile、final发布。 - 类库级(
java.util.concurrent):Lock、Condition、AQS 同步器(Semaphore、CountDownLatch…),原子类与并发容器,阻塞队列,线程池,CompletableFuture等。 - 设计层:无锁/低锁、复制写(COW)、分段锁、读写分离、基于消息/队列的并发(异步解耦)。
4. synchronized 关键字
用法
- 实例/类方法修饰;同步代码块
synchronized(obj) { ... }。 - 内置监视器(Monitor),进入时独占,退出时释放。
优点
- 语义简单、异常安全、可重入;与 JMM 深度集成,释放锁→刷新写、获取锁→读取最新。
- 代码可读性强,适合临界区较短的互斥。
缺点
- 无法中断等待,无法设置超时;
- 仅支持单条件队列(需
wait/notify搭配,API 较原始)。
示例:
class Counter {
private int c;
public synchronized void inc() { c++; }
public synchronized int get() { return c; }
}
何时优先:同步块短、结构简单、无需超时/中断、无需多条件队列。
5. Lock 家族:ReentrantLock / ReadWriteLock / StampedLock
5.1 ReentrantLock
- 特性:可重入、可选择公平/非公平、支持可中断获取、可超时获取、支持多个
Condition。 - 优点:比
synchronized更灵活;支持tryLock()避免死锁;可实现复杂的等待/通知。 - 缺点:需要手动
unlock();滥用会导致复杂度上升。
示例(避免死锁):
if (lock1.tryLock(50, TimeUnit.MILLISECONDS)) {
try {
if (lock2.tryLock(50, TimeUnit.MILLISECONDS)) {
try { /* ... */ }
finally { lock2.unlock(); }
}
} finally { lock1.unlock(); }
}
5.2 ReadWriteLock(ReentrantReadWriteLock)
- 读多写少场景:多个读并行,写互斥。
- 优点:读吞吐提升明显。
- 缺点:可能写饥饿(可用公平锁缓解);读期间升级为写不支持(需释放后再获取写)。
5.3 StampedLock
- 三种模式:写锁、悲观读锁、乐观读(
tryOptimisticRead)。 - 优点:读多写少下,乐观读无需加锁,性能更好。
- 缺点:不可重入、不与
Condition协作、API 更复杂。
示例(乐观读):
long stamp = lock.tryOptimisticRead();
int x = this.x, y = this.y;
if (!lock.validate(stamp)) { // 有写入发生,回退到悲观读
stamp = lock.readLock();
try { x = this.x; y = this.y; } finally { lock.unlockRead(stamp); }
}
6. 条件队列与线程协作:wait/notify、Condition、LockSupport
wait/notify(配合 synchronized)
- 优点:JDK 早期机制,直接作用于对象监视器。
- 缺点:易错(丢通知/虚假唤醒),写法繁琐;建议总是用
while检查条件。
synchronized (lock) {
while (!ready) lock.wait();
// do work
}
Condition(配合 Lock)
- 优点:多个条件队列、精确唤醒(
signal/signalAll),与ReentrantLock配合强大。 - 缺点:需要管理锁与条件的匹配关系。
LockSupport
- 低层 API(
park/unpark),AQS 的基石;不要直接与synchronized混用。
7. AQS 同步器与并发工具:Semaphore / CountDownLatch / CyclicBarrier / Phaser / Exchanger
AQS(AbstractQueuedSynchronizer) 是 JUC 多数同步器的基础。
-
Semaphore:计数信号量,限流/限并发。
- 优点:实现连接池/限速简单有效;
- 缺点:只能控制许可数量,无法表达复杂条件;
Semaphore sem = new Semaphore(10); sem.acquire(); try { /* 临界区 */ } finally { sem.release(); } -
CountDownLatch:一次性闭锁,等待若干事件完成。
- 优点:线程安全版“等 N 件事”;
- 缺点:一次性,不可重用。
-
CyclicBarrier:可重用栅栏,N 个线程相互等待;可附加 barrierAction。
- 优点:阶段性并行算法(如分片计算 → 汇总);
- 缺点:某线程超时/异常会导致
BrokenBarrierException。
-
Phaser:分阶段栅栏,动态注册/注销,适合复杂的多阶段工作流。
- 优点:更灵活的多轮次协作;
- 缺点:API 相对复杂。
-
Exchanger:成对线程交换数据。
- 优点:双缓冲、生产-消费配对;
- 缺点:仅适用于两两配对的场景。
8. 原子类与 CAS:AtomicXXX、LongAdder/LongAccumulator、ABA 问题
CAS(Compare-And-Set)
- 优点:无需加锁,冲突低时性能好。
- 缺点:自旋重试消耗 CPU;ABA 问题;只适合简单状态更新。
AtomicXXX
AtomicInteger/Long/Boolean/Reference、AtomicStampedReference(带版本解决 ABA)、AtomicMarkableReference。- 优点:简单无锁,适合计数器/标志位/单引用更新。
- 缺点:难以表达复合不变量(需要
AtomicReference持有不可变对象)。
LongAdder / LongAccumulator
- 思想:热点分散(分片 Cell),高并发累加更快。
- 优点:高并发下优于
AtomicLong; - 缺点:读取是近似值(在没有并发写时才准确)。
示例:
LongAdder adder = new LongAdder();
adder.increment();
long v = adder.sum(); // 近似累计值
9. 并发容器与阻塞队列
并发容器
-
ConcurrentHashMap:高并发哈希表,支持computeIfAbsent/merge。- 优点:扩容、分段/桶级并发(JDK8 之后基于 CAS + 红黑树化优化);
- 缺点:复合操作仍需外部同步(如“检查后执行”)。
-
CopyOnWriteArrayList/Set:写时复制,读无锁。- 优点:读多写少非常快;迭代无锁且弱一致;
- 缺点:写开销大、内存占用高,不适合大对象/频繁写。
阻塞队列(生产者-消费者)
ArrayBlockingQueue:有界数组;LinkedBlockingQueue:链表,默认近乎无界(不指定容量会很大);SynchronousQueue:无容量,交接式;适配“直接移交”模型;DelayQueue/PriorityBlockingQueue/LinkedTransferQueue:定时/优先级/传递式等特殊场景。
建议:生产者-消费者优先选有界队列,并在饱和时给出降级/拒绝策略。
10. ThreadLocal 的使用与陷阱
- 用途:为每个线程提供独立副本(如格式化器、上下文、TraceId)。
- 陷阱:在线程池中,线程复用 → 不清理会内存泄漏/脏数据。
- 建议:使用
try { set(...) } finally { remove(); };必要时使用TransmittableThreadLocal传播上下文。
11. 线程池与任务调度:ThreadPoolExecutor / ScheduledThreadPoolExecutor / ForkJoinPool / CompletableFuture
11.1 ThreadPoolExecutor 关键参数
corePoolSize / maximumPoolSize / keepAliveTime / workQueue / threadFactory / handler。- 拒绝策略:
AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。 - 创建建议:不要直接用
Executors的快捷方法(如newFixedThreadPool默认无界队列),改用ThreadPoolExecutor明确边界。
尺寸估算(经验):
- CPU 密集:
core ≈ CPU核数; - I/O 密集:
core ≈ CPU核数 × (1 + 平均等待时间/平均计算时间)。
11.2 ScheduledThreadPoolExecutor
- 定时/周期任务;注意任务执行时间超过周期时的堆积与并发策略。
11.3 ForkJoinPool 与并行计算
- 任务分治(work-stealing);适合大任务拆分小任务的 CPU 密集型场景。
11.4 CompletableFuture(异步编排)
- 优点:链式编排、组合计算、异常处理(
exceptionally/handle),可指定自定义执行器; - 陷阱:默认使用公共 ForkJoinPool,易与框架抢资源;阻塞
join/get可能导致线程饥饿。
示例:自定义执行器 + 组合
ExecutorService ioPool = new ThreadPoolExecutor(
8, 16, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(200));
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> loadA(), ioPool);
CompletableFuture<String> b = CompletableFuture.supplyAsync(() -> loadB(), ioPool);
String result = a.thenCombine(b, (x, y) -> x + y)
.orTimeout(2, TimeUnit.SECONDS)
.exceptionally(ex -> "fallback")
.join();
12. 无锁/低锁思路与性能优化
- 分段与拆分:将热点拆成多个分片(如
LongAdder)。 - 局部性优化:减少共享、增加不可变对象;
- 读写分离:
ReadWriteLock或 快照+增量; - 批量化:降低锁获取频率(批处理插入/聚合);
- 避免伪共享:必要时使用填充(
@sun.misc.Contended在某些版本需 JVM 选项)或结构化内存布局。
13. 典型并发 Bug 与排错方法
13.1 常见问题
-
死锁:循环等待;
- 防治:约定加锁顺序、
tryLock+ 超时、细化锁粒度、避免在持锁区做 I/O。
- 防治:约定加锁顺序、
-
活锁/饥饿:线程过度礼让/优先级不当;
-
可见性缺陷:缺
volatile或发布不安全; -
并发容器误用:
computeIfAbsent里做阻塞操作导致扩散阻塞。
13.2 观测与工具
- 线程转储:
jstack/jcmd Thread.print→ 定位死锁(会直接打印 “Found one Java-level deadlock”)。 - JFR/Profiler:Java Flight Recorder、Async-profiler 观察锁竞争、阻塞点、CPU 画像。
- 日志与指标:关键路径埋点、超时重试日志、队列长度/拒绝次数/耗时直方图。
14. 选型与实践清单(速查表)
| 需求 | 首选 | 替代/补充 | 注意点 |
|---|---|---|---|
| 简单互斥 | synchronized | ReentrantLock | 临界区短、无需超时/中断 → 用 synchronized |
| 需要可中断/超时/多条件 | ReentrantLock+Condition | — | try{ lock } finally { unlock } |
| 读多写少 | ReentrantReadWriteLock | StampedLock | StampedLock 不可重入;乐观读需校验 |
| 限流/限并发 | Semaphore | 线程池 + 有界队列 | 许可数量与降级策略 |
| 等待多事件完成 | CountDownLatch | Phaser | Latch 一次性,Phaser 可复用 |
| 阶段性协作 | CyclicBarrier | Phaser | 超时/异常会破栅栏 |
| 双线程数据交换 | Exchanger | 队列 | 只适用于成对线程 |
| 高并发计数 | LongAdder | AtomicLong | 读取近似值;统计结束再读 |
| 生产者-消费者 | BlockingQueue | Disruptor(第三方) | 使用有界队列与拒绝策略 |
| 异步编排 | CompletableFuture | Reactor/RxJava | 指定自定义执行器,避免公共池争用 |
工程化建议:
- 统一线程池创建工厂(命名、守护线程、异常处理、MDC/Trace 传递)。
- 所有阻塞操作设定超时;关键路径设熔断/限流/降级策略。
- 监控:线程池活跃数、队列长度、任务拒绝、等待与执行耗时分布。
15. 面试要点与手撕小题
- volatile 的作用与局限:只保证可见性与有序性,不保证复合操作原子性。
- synchronized vs ReentrantLock:是否可中断/超时、公平性、条件队列、手动释放。
- AQS 的核心:基于 CLH 队列,
state表示同步状态,独占/共享两种获取与释放语义。 - Semaphore 与 CountDownLatch 区别:许可 vs 计数;多次获取/释放 vs 一次性倒数。
- CyclicBarrier 与 Phaser 区别:Barrier 固定参与者;Phaser 动态注册/多阶段。
- LongAdder 适用场景:高并发计数;为何优于 AtomicLong(热点分散)。
- CompletableFuture 常见坑:公共池、阻塞 join 造成饥饿、异常传播;如何指定线程池与做超时/降级。
手撕示例:有界生产-消费 + 超时退出
class BoundedQueue<T> {
private final BlockingQueue<T> q = new ArrayBlockingQueue<>(1000);
public boolean put(T t, long timeout, TimeUnit unit) throws InterruptedException {
return q.offer(t, timeout, unit); // 失败可触发降级
}
public T take(long timeout, TimeUnit unit) throws InterruptedException {
return q.poll(timeout, unit); // 超时可做空闲处理
}
}
5万+

被折叠的 条评论
为什么被折叠?



