1. Java并发包概览与核心价值
Java并发包(java.util.concurrent)自JDK 1.5引入以来,已成为处理多线程编程的事实标准工具集。这个包的设计初衷是解决原生synchronized和wait/notify机制存在的三大痛点:一是锁粒度控制不灵活,二是缺乏高级同步原语,三是原子操作需要自行实现。我在实际企业级开发中发现,合理使用并发包能提升30%-50%的线程安全代码性能。
当前主流Java版本(JDK 11+)的并发包主要包含这些核心组件:
- 执行框架(ExecutorService等)
- 锁机制(ReentrantLock、StampedLock)
- 原子变量(AtomicInteger等)
- 并发集合(ConcurrentHashMap等)
- 同步器(CountDownLatch等)
重要提示:虽然并发包强大,但过度使用反而会增加复杂度。建议先评估是否真的需要并发,再选择合适的工具。
2. Callable与Future深度解析
2.1 Callable的诞生背景
相比Runnable只能运行无返回值的任务,Callable接口通过泛型定义了带返回值的线程任务。这个设计解决了实际业务中常见的"异步计算+结果获取"需求场景。比如在电商系统中,我们经常需要并行查询多个商品的库存和价格。
java复制// 典型Callable使用示例
Callable<Integer> task = () -> {
Thread.sleep(1000);
return ThreadLocalRandom.current().nextInt(1, 100);
};
2.2 Future的运作机制
Future接口提供了检查计算是否完成、等待计算完成以及获取计算结果的方法。其核心实现类FutureTask内部维护了一个状态机:
code复制NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
2.3 实战中的坑与技巧
- 超时控制:务必使用带超时的get方法,避免线程永久阻塞
java复制try { result = future.get(3, TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true); } - 异常处理:Callable抛出的异常会被包装在ExecutionException中
- 性能监控:可通过继承FutureTask覆盖done()方法添加监控逻辑
3. ReentrantLock的进阶使用
3.1 与synchronized的对比
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 实现机制 | AQS队列 | JVM内置 |
| 可中断 | 支持 | 不支持 |
| 公平锁 | 可配置 | 非公平 |
| 条件变量 | 支持多个 | 单个 |
| 锁获取超时 | 支持 | 不支持 |
3.2 条件变量的妙用
Condition接口实现了管程模型的等待/通知机制。典型的生产者-消费者模式实现:
java复制class Buffer {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putPtr] = x;
if (++putPtr == items.length) putPtr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
}
3.3 锁的最佳实践
- 锁顺序:多个锁必须按固定顺序获取,避免死锁
- tryLock使用:非阻塞式获取锁适合心跳检测等场景
- 锁分段:大集合可拆分为多个锁段提升并发度
4. 原子类原理与性能优化
4.1 CAS底层实现
原子类基于CPU的CAS指令(如x86的CMPXCHG),其伪代码如下:
java复制public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
4.2 常见原子类对比
- 基本类型:AtomicInteger、AtomicLong等
- 数组类型:AtomicIntegerArray等
- 引用类型:AtomicReference等
- 字段更新:AtomicIntegerFieldUpdater等
4.3 高并发场景优化
- LongAdder替代AtomicLong:在高度竞争环境下性能更好
- 消除伪共享:使用@Contended注解(JDK8+)
java复制@Contended public class AtomicLong { private volatile long value; } - 批量操作:利用accumulateAndGet等方法减少CAS次数
5. 信号量与栅栏的实战应用
5.1 Semaphore的流量控制
数据库连接池的典型实现:
java复制public class ConnectionPool {
private final Semaphore semaphore;
private final BlockingQueue<Connection> pool;
public Connection getConnection() throws InterruptedException {
semaphore.acquire();
return pool.take();
}
public void releaseConnection(Connection conn) {
pool.offer(conn);
semaphore.release();
}
}
5.2 CountDownLatch的并行初始化
系统启动时加载多个模块:
java复制CountDownLatch latch = new CountDownLatch(3);
// 三个线程分别初始化模块
executor.execute(() -> { initDB(); latch.countDown(); });
executor.execute(() -> { initCache(); latch.countDown(); });
executor.execute(() -> { initConfig(); latch.countDown(); });
// 等待所有初始化完成
latch.await(10, TimeUnit.SECONDS);
5.3 CyclicBarrier的批量处理
日志批量写入场景:
java复制CyclicBarrier barrier = new CyclicBarrier(5, () -> flushBufferToDisk());
void log(String message) {
buffer.add(message);
if (buffer.size() >= 5) {
barrier.await(); // 触发批量写入
}
}
6. ConcurrentHashMap的设计哲学
6.1 版本演进对比
| JDK版本 | 实现方式 | 并发度 | 重要改进 |
|---|---|---|---|
| 1.5-1.7 | 分段锁(Segment) | 16 | 减小锁粒度 |
| 1.8+ | CAS+synchronized | 桶级别 | 链表转红黑树,减少内存消耗 |
6.2 关键源码解析
putVal方法的核心逻辑:
- 计算key的hash值
- 表为空则初始化(CAS)
- 桶为空时CAS插入
- 桶不为空时synchronized锁住头节点
- 处理链表/红黑树插入
6.3 使用注意事项
- size()的代价:JDK8中是遍历计数,高并发时慎用
- computeIfAbsent死锁:不要在映射函数中修改当前map
- 扩容影响:当达到阈值(默认0.75)时会触发扩容
7. 并发工具的综合选型策略
根据业务场景选择合适工具的判断流程:
-
是否需要等待结果?
- 是 → 使用Future/Callable
- 否 → 进入下一步
-
是否需要限制并发数?
- 是 → Semaphore
- 否 → 进入下一步
-
是否需要等待多个任务完成?
- 是 → CountDownLatch/CyclicBarrier
- 否 → 进入下一步
-
是否需要保证原子性?
- 简单类型 → 原子类
- 复杂对象 → 进入下一步
-
是否需要互斥访问?
- 短期锁定 → synchronized
- 需要高级特性 → ReentrantLock
在分布式系统设计中,这些并发工具通常需要与分布式锁(如Redis实现)配合使用。我曾在一个订单系统中使用ReentrantLock+Redis红锁实现了跨JVM的库存扣减,关键是要设置合理的锁超时时间。
