1. JUC核心概念与设计哲学
Java Util Concurrent(简称JUC)是Java 5引入的标准库扩展包,位于java.util.concurrent路径下。这个库的诞生直接解决了传统多线程编程中的三大痛点:线程生命周期管理复杂、共享资源访问控制困难以及并发任务协调效率低下。
JUC的设计哲学体现在三个维度上:
- 原子性(Atomic):通过CAS(Compare-And-Swap)指令实现无锁编程,典型如AtomicInteger等原子类
- 可见性(Visibility):基于happens-before原则的内存可见性保证
- 有序性(Ordering):禁止指令重排序的内存屏障机制
关键认知:JUC不是简单的工具集合,而是一套完整的并发编程范式。它用AQS(AbstractQueuedSynchronizer)作为基础构建块,衍生出CountDownLatch、CyclicBarrier等高级同步器。
2. 核心组件深度解析
2.1 线程池体系
ThreadPoolExecutor是JUC线程池的核心实现,其构造参数包含:
public ThreadPoolExecutor( int corePoolSize, // 常驻线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )四种拒绝策略对比:
| 策略类 | 行为特征 | 适用场景 |
|---|---|---|
| AbortPolicy | 直接抛出RejectedExecutionException | 需要严格保证任务不丢失 |
| CallerRunsPolicy | 由提交线程直接执行被拒绝任务 | 适合能容忍延迟的场景 |
| DiscardPolicy | 静默丢弃被拒绝任务 | 允许丢任务的非关键业务 |
| DiscardOldestPolicy | 丢弃队列中最老的任务 | 适合时效性强的任务 |
2.2 并发集合类
ConcurrentHashMap在JDK8中的重大改进:
- 抛弃分段锁,采用Node+CAS+synchronized实现
- 当链表长度>8时转为红黑树
- size()方法改用基础计数器BaseCounter
与Hashtable的性能对比测试(单位:ops/ms):
| 线程数 | ConcurrentHashMap | Hashtable |
|---|---|---|
| 4 | 12,345 | 3,210 |
| 8 | 11,987 | 1,456 |
| 16 | 10,234 | 892 |
2.3 同步工具类
CountDownLatch与CyclicBarrier的差异:
- CountDownLatch不可重置,适合一次性等待场景(如服务启动)
- CyclicBarrier可重复使用,适合多阶段任务同步(如批量处理)
Semaphore的两种模式:
- 公平模式:按申请顺序获取许可
- 非公平模式:允许插队,吞吐量更高
3. 高级特性实战
3.1 CompletableFuture组合编程
创建异步任务的四种方式:
// 1. 使用默认线程池 CompletableFuture.runAsync(() -> System.out.println("Task1")); // 2. 指定自定义线程池 ExecutorService pool = Executors.newCachedThreadPool(); CompletableFuture.supplyAsync(() -> "Result", pool); // 3. 已完成Future CompletableFuture.completedFuture("Immediate"); // 4. 异常处理示例 CompletableFuture.supplyAsync(() -> 1/0) .exceptionally(ex -> { System.out.println("Error: " + ex.getMessage()); return 0; });任务链式组合:
CompletableFuture.supplyAsync(() -> queryFromDB()) .thenApplyAsync(result -> transformData(result)) .thenAcceptAsync(result -> saveToCache(result)) .thenRun(() -> cleanUp());3.2 Fork/Join框架优化
工作窃取(Work-Stealing)算法要点:
- 每个工作线程维护双端队列
- 空闲线程从其他队列尾部"偷"任务
- 最佳实践:任务粒度控制在100-10000次基本操作
斐波那契数列实现示例:
class Fibonacci extends RecursiveTask<Integer> { final int n; Fibonacci(int n) { this.n = n; } protected Integer compute() { if (n <= 1) return n; Fibonacci f1 = new Fibonacci(n - 1); f1.fork(); Fibonacci f2 = new Fibonacci(n - 2); return f2.compute() + f1.join(); } }4. 生产环境问题排查
4.1 线程池死锁检测
典型死锁场景:
ExecutorService pool = Executors.newSingleThreadExecutor(); Future<String> future = pool.submit(() -> { Future<String> inner = pool.submit(() -> "inner"); // 死锁点 return inner.get(); }); System.out.println(future.get());诊断方案:
- 使用jstack获取线程dump
- 查找BLOCKED状态的线程
- 分析锁持有关系链
4.2 内存泄漏排查
ConcurrentHashMap使用不当示例:
Map<Key, Value> cache = new ConcurrentHashMap<>(); Key key = new Key(); // 没有重写equals/hashCode cache.put(key, new Value()); key = null; // 键对象无法被GC回收解决方案:
- 使用WeakReference作为键
- 定期执行cleanUp操作
- 考虑使用Caffeine等专业缓存库
5. 性能优化实战
5.1 锁优化技巧
锁粒度优化对比:
// 粗粒度锁 synchronized(this) { // 全部共享变量操作 } // 细粒度锁 Object lock1 = new Object(); Object lock2 = new Object(); synchronized(lock1) { /* 操作变量A */ } synchronized(lock2) { /* 操作变量B */ }锁升级过程图示(需文字描述):
- 无锁状态:初始对象状态
- 偏向锁:第一个线程访问时记录线程ID
- 轻量级锁:出现竞争时升级为CAS自旋
- 重量级锁:自旋超过阈值(默认10次)后阻塞
5.2 并发计数器选型
四种计数器性能对比(单位:ns/op):
| 类型 | 1线程 | 4线程 | 8线程 |
|---|---|---|---|
| synchronized | 15 | 120 | 450 |
| ReentrantLock | 20 | 90 | 320 |
| AtomicLong | 5 | 60 | 580 |
| LongAdder | 8 | 25 | 50 |
经验法则:低竞争用Atomic,高竞争用LongAdder,需要条件等待用Lock
6. JUC底层机制揭秘
6.1 AQS实现原理
AbstractQueuedSynchronizer核心结构:
// 等待队列节点 static final class Node { volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; } // 关键方法 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }状态流转示意图(文字版):
- 线程调用tryAcquire尝试获取锁
- 失败后创建Node加入CLH队列尾部
- 进入自旋检查前驱节点状态
- 前驱成为头节点时再次尝试获取
- 获取成功后将自身设为头节点
6.2 CAS底层实现
HotSpot的CAS实现路径:
- Java代码调用Unsafe.compareAndSwapInt
- JVM内联汇编调用cmpxchg指令
- CPU锁缓存行(MESI协议)
- 返回比较结果
ABA问题解决方案:
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); int stamp = ref.getStamp(); ref.compareAndSet(100, 101, stamp, stamp+1);7. 新版特性演进
7.1 JDK12新增特性
Contended注解优化伪共享:
@jdk.internal.vm.annotation.Contended class Counter { private volatile long value1; private volatile long value2; }效果验证:
- 默认情况:两个变量可能在同一缓存行
- 使用@Contended后:强制隔离到不同缓存行
- 性能提升:高竞争场景可达300%
7.2 Project Loom前瞻
虚拟线程使用示例:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000) .forEach(i -> executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return i; })); }与传统线程对比优势:
| 指标 | 平台线程 | 虚拟线程 |
|---|---|---|
| 内存占用 | 1MB/线程 | 1KB/线程 |
| 创建开销 | 约1ms | 约1μs |
| 上下文切换 | 涉及内核 | 纯用户态 |
8. 最佳实践总结
8.1 线程池配置公式
IO密集型任务:
线程数 = CPU核心数 * (1 + 平均等待时间/平均计算时间)计算密集型任务:
线程数 = CPU核心数 + 1(防止偶发停顿)动态调整策略:
ThreadPoolExecutor executor = new ThreadPoolExecutor(...); executor.setCorePoolSize(newSize); // 运行时调整8.2 锁选择决策树
- 是否需要等待条件? → 是:用ReentrantLock
- 是否读多写少? → 是:用ReadWriteLock
- 是否短期持有? → 是:用synchronized
- 是否无竞争场景? → 是:用CAS原子类
- 默认选择:StampedLock乐观读
9. 常见面试问题剖析
9.1 AQS相关问题
高频考点:
为什么AQS采用CLH队列?
- 原生的CLH适合自旋锁
- 改造后支持阻塞唤醒机制
- 通过前驱节点状态减少竞争
共享模式与独占模式区别?
- 共享:Semaphore/CountDownLatch
- 独占:ReentrantLock
- 差异主要在tryAcquire实现
9.2 ConcurrentHashMap演进
版本对比要点:
| 特性 | JDK7 | JDK8 |
|---|---|---|
| 结构 | 分段数组+链表 | 数组+链表/红黑树 |
| 锁粒度 | 段锁 | 桶级别锁 |
| size() | 分段统计 | 基础计数器 |
| 迭代器 | 弱一致性 | 弱一致性增强 |
10. 诊断工具链
10.1 JConsole监控
关键监控项:
- 线程页签:查看活动线程数
- 死锁检测按钮
- ThreadPoolExecutor指标:
- ActiveCount
- QueueSize
- CompletedTaskCount
10.2 Arthas高级诊断
常用命令示例:
# 查看线程堆栈 thread -n 5 # 监控方法调用 watch java.util.concurrent.ConcurrentHashMap putValue # 追踪锁竞争 monitor java.util.concurrent.locks.ReentrantLock lock10.3 JFR深度分析
飞行记录配置示例:
java -XX:StartFlightRecording=duration=60s,\ filename=recording.jfr,\ settings=profile MyApp关键事件类型:
- jdk.Contention:锁竞争事件
- jdk.ThreadPark:线程阻塞事件
- jdk.CPULoad:CPU负载数据