1. JUC并发编程核心概念解析
Java并发编程中的JUC(java.util.concurrent)包是Java 5引入的重要并发工具库,它提供了一系列比原生synchronized更强大的并发控制机制。在实际高并发场景中,JUC组件往往能带来显著的性能提升。以CountDownLatch为例,它的核心原理是通过一个计数器来实现线程协调:
// 典型用法示例 CountDownLatch latch = new CountDownLatch(3); new Thread(() -> { // 任务执行 latch.countDown(); }).start(); latch.await(); // 阻塞直到计数器归零这种机制相比传统的wait/notify有三大优势:
- 计数器状态可视化,调试更方便
- 不需要 synchronized 同步块
- 支持一次性释放多个等待线程
关键经验:在初始化CountDownLatch时,计数器值应该等于需要等待的独立任务数,而不是线程数。我曾在一个订单处理系统中错误设置为线程池大小,导致永远无法触发完成条件。
2. 原子操作类深度剖析
java.util.concurrent.atomic包下的原子类使用CAS(Compare-And-Swap)实现无锁线程安全。以AtomicInteger为例:
AtomicInteger counter = new AtomicInteger(0); // 线程安全的自增 int newValue = counter.incrementAndGet();其底层实现依赖于Unsafe类的compareAndSwapInt方法,该方法是native方法,最终会调用CPU的原子指令。在x86架构上对应的是LOCK CMPXCHG指令。
性能对比测试数据:
| 操作类型 | 100万次操作耗时(ms) |
|---|---|
| synchronized | 120 |
| AtomicInteger | 45 |
| volatile | 30(但非线程安全) |
踩坑记录:CAS存在ABA问题,即值从A→B→A的变化无法被检测到。在支付状态变更场景中,我曾因此导致重复扣款。解决方案是使用AtomicStampedReference。
3. 并发容器实战技巧
JUC提供的并发容器在HashMap等传统集合的基础上实现了线程安全:
3.1 ConcurrentHashMap分段锁机制
JDK7中的ConcurrentHashMap采用分段锁设计,默认分为16个Segment。而JDK8之后改为:
- 数组+链表+红黑树结构
- CAS+synchronized实现
- 粒度更细的锁控制
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); // 线程安全的putIfAbsent map.putIfAbsent("key", 1);3.2 CopyOnWriteArrayList适用场景
适合读多写少的场景,写入时复制整个数组:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("item"); // 触发数组复制性能提示:当预期修改操作超过总操作量的10%时,应考虑其他方案,因为频繁复制会导致GC压力。
4. 线程池最佳实践
ThreadPoolExecutor的核心参数配置直接影响系统稳定性:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60, // 空闲时间(秒) TimeUnit.SECONDS, new LinkedBlockingQueue<>(100) // 任务队列 );配置经验公式:
- CPU密集型:核心线程数 = CPU核数 + 1
- IO密集型:核心线程数 = CPU核数 × 2
监控建议:继承ThreadPoolExecutor并重写beforeExecute/afterExecute方法,添加监控日志。我们曾通过这种方式发现任务执行时间异常导致的线程堆积问题。
5. Lock显式锁进阶用法
ReentrantLock相比synchronized提供了更灵活的控制:
ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { while(!conditionMet) { condition.await(); // 类似Object.wait() } // 业务逻辑 condition.signal(); // 类似Object.notify() } finally { lock.unlock(); // 必须放在finally块 }锁选型决策树:
- 需要尝试获取锁?→ ReentrantLock.tryLock()
- 需要公平性?→ new ReentrantLock(true)
- 需要读写分离?→ ReentrantReadWriteLock
6. 并发编程常见陷阱
6.1 死锁检测与预防
使用jstack检测死锁:
jstack -l <pid> | grep -A 10 deadlock预防策略:
- 按固定顺序获取多把锁
- 设置锁超时时间
- 使用Lock.tryLock()
6.2 线程上下文切换开销
测试表明,当线程数超过CPU核心数2倍时,上下文切换开销会显著增加。可以通过以下命令监控:
vmstat 1 # 查看cs字段(上下文切换次数)7. 性能优化实战案例
在某电商秒杀系统中,我们通过以下JUC优化将QPS从500提升到3000+:
使用LongAdder替代AtomicLong计数器
- 优点:分段累加减少CAS竞争
- 缺点:空间消耗稍大
采用ConcurrentHashMap缓存商品库存
- 使用computeIfAbsent原子化初始化
- 避免双重检查锁定模式
自定义ThreadPoolExecutor拒绝策略
- 记录拒绝的任务信息
- 触发降级处理逻辑
new ThreadPoolExecutor.CallerRunsPolicy() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 自定义记录和降级逻辑 log.warn("Task rejected: " + r); fallbackProcessor.process(r); } }对于高并发场景下的资源竞争,我发现采用分层加锁策略往往能取得更好效果。比如在订单系统中,先对用户ID哈希取模分段,再在段内加细粒度锁,这样既保证了线程安全,又避免了全局锁的性能瓶颈。