第 23 章:并发容器与原子类
学习目标
- 掌握 ConcurrentHashMap / CopyOnWriteArrayList
- 学会用 AtomicInteger / AtomicLong 做无锁计数
- 理解 CountDownLatch / CompletableFuture 简化并发
一、并发容器速查
HashMap / ArrayList 都是线程不安全的,多线程下要用并发版本:
| 场景 | 线程不安全 | 线程安全(推荐) |
|---|---|---|
| 键值对 | HashMap | ConcurrentHashMap |
| 列表(读多写少) | ArrayList | CopyOnWriteArrayList |
| 队列 | LinkedList | ConcurrentLinkedQueue / ArrayBlockingQueue |
| 有序键值 | TreeMap | ConcurrentSkipListMap |
java
// ✅ 多线程下用 ConcurrentHashMap
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1); // 线程安全,无需加锁
int v = map.getOrDefault("a", 0);
// ✅ 读多写少场景(如配置、黑名单)
List<String> blacklist = new CopyOnWriteArrayList<>();
blacklist.add("user1"); // 写时复制整个数组,读无锁,极快⚠️ 坑 1:不要用
Hashtable/Collections.synchronizedMap——它们锁整个表,性能差。直接用ConcurrentHashMap。
二、原子类(无锁计数器)
java
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // ++i
count.getAndIncrement(); // i++
count.addAndGet(10); // += 10
count.compareAndSet(5, 100); // CAS:期望 5 就改成 100
// 高并发计数用 LongAdder(分段累加,比 AtomicLong 快 5-10 倍)
LongAdder adder = new LongAdder();
adder.increment();
long sum = adder.sum();⚠️ 坑 2:
count++在多线程下不是原子的(3 步:read → +1 → write),必须用incrementAndGet()才有原子性。
三、CountDownLatch(倒计时器)
等 N 个任务都完成,再继续:
java
CountDownLatch latch = new CountDownLatch(3); // 计数 3
for (int i = 1; i <= 3; i++) {
new Thread(() -> {
try {
// 干活
} finally {
latch.countDown(); // 计数 -1
}
}).start();
}
latch.await(); // 阻塞,等计数到 0 才继续
System.out.println("所有任务完成");四、CompletableFuture(异步编排,JDK 8+)
比 Future 更强大,支持链式调用:
java
// 异步执行 + 链式处理
CompletableFuture.supplyAsync(() -> queryUser(userId)) // ① 异步查用户
.thenApply(user -> queryOrders(user.getId())) // ② 拿到用户后查订单
.thenAccept(orders -> System.out.println(orders)) // ③ 最终处理
.exceptionally(ex -> { // ④ 异常兜底
log.error("查询失败", ex);
return null;
});
// 等待所有完成
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2, future3);
all.join(); // 阻塞等所有完成和线程池配合:
java
ExecutorService pool = Executors.newFixedThreadPool(4);
CompletableFuture.supplyAsync(() -> queryData(), pool) // 用指定线程池
.thenAccept(data -> process(data));五、并发实战:批量查询
java
// ❌ 串行:慢
for (Long id : ids) {
User u = userService.findById(id);
process(u);
}
// ✅ 并行:用 CompletableFuture
List<CompletableFuture<User>> futures = ids.stream()
.map(id -> CompletableFuture.supplyAsync(
() -> userService.findById(id), pool))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
List<User> users = futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());六、什么时候用什么
| 场景 | 用什么 |
|---|---|
| 多线程改 Map | ConcurrentHashMap |
| 高并发计数 | LongAdder |
| 简单计数 / 标志位 | AtomicInteger |
| 等 N 个任务完成 | CountDownLatch |
| 异步任务编排 | CompletableFuture |
| 读多写少 List | CopyOnWriteArrayList |
| 生产消费队列 | ArrayBlockingQueue |
七、本章小结
| 要点 | 关键 |
|---|---|
| 并发 Map | ConcurrentHashMap,别用 Hashtable / synchronizedMap |
| 原子类 | AtomicInteger / LongAdder,无锁快 |
| 倒计时 | CountDownLatch 等 N 个任务完成 |
| 异步编排 | CompletableFuture 链式调用 |
| 高并发计数 | LongAdder,比 AtomicLong 快 5-10 倍 |
动手练习
- 并发计数器:1000 个线程用
AtomicInteger各加 100 次,验证最终 100000 - 批量查询:用
CompletableFuture.allOf并行查 10 个用户 - 黑名单:用
CopyOnWriteArrayList实现一个读多写少的黑名单