线程池与并发工具
使用 Executors、ThreadPoolExecutor、CountDownLatch、ConcurrentHashMap
线程池与并发工具
每个任务都 new Thread() 是很浪费的——线程的创建和销毁开销不小。线程池预创建一批线程循环使用,你来任务它来跑,跑完不销毁,下个任务接着用。
学完本章你将: 使用线程池、理解核心参数、掌握 CountDownLatch 和 ConcurrentHashMap。
为什么要用线程池
不用线程池:新任务 → new Thread() → 执行 → 销毁(1000个任务=1000次创建销毁)
使用线程池:新任务 → 丢进队列 → 空闲线程自动取任务执行(只创建一次线程)
线程池的核心价值:复用线程 + 控制并发数(防止无限创建线程导致 OOM)。
Executor 框架
java
// 最常用的几种线程池(实际项目中避免用 Executors,后面解释)
ExecutorService fixed = Executors.newFixedThreadPool(4); // 固定 4 个线程
ExecutorService cached = Executors.newCachedThreadPool(); // 自动扩缩容
ExecutorService single = Executors.newSingleThreadExecutor(); // 单线程
// 提交任务
for (int i = 0; i < 10; i++) {
int taskId = i;
fixed.submit(() -> {
System.out.println("任务 " + taskId + " → " +
Thread.currentThread().getName());
});
}
// ⚠️ 用完必须关闭!
fixed.shutdown(); // 温柔关闭(跑完队列中任务)
fixed.awaitTermination(1, TimeUnit.MINUTES); // 等最多1分钟
ThreadPoolExecutor —— 生产环境推荐
Executors 创建的线程池有无界队列风险(任务堆积可能 OOM)。生产环境应该直接 new ThreadPoolExecutor,了解每个参数的含义。
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // corePoolSize:平时常驻的线程数
4, // maxPoolSize:忙时最多扩展到多少
60, TimeUnit.SECONDS, // 超过核心数的线程空闲多久后回收
new LinkedBlockingQueue<>(100), // 任务队列(满了才创建新线程)
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:队列满了让主线程跑
);
参数执行逻辑:
- 任务来了 → 核心线程有空闲?直接给
- 核心线程都忙 → 放入队列
- 队列也满了 → 创建新线程(直到 maxPoolSize)
- 线程也满了 → 执行拒绝策略
CountDownLatch —— 等大家都完成
一个线程需要等待其他 N 个线程都完成后再继续,比如主线程等所有子任务跑完汇总结果。
java
int taskCount = 3;
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
new Thread(() -> {
doHeavyWork();
latch.countDown(); // 我完成了,计数 -1
}).start();
}
latch.await(); // 阻塞直到计数归零(3→2→1→0)
System.out.println("全部完成,汇总结果!");
📌 计数器归零后 latch 就失效了,不能重置。 需要重复使用的话用
CyclicBarrier。
ConcurrentHashMap —— 线程安全的 HashMap
Hashtable 和 Collections.synchronizedMap() 用全局锁,同一时刻只有一个线程能读写,并发一高就成瓶颈。ConcurrentHashMap 用分段锁(JDK8+ 改为 CAS + synchronized 只锁单个桶),并发读写性能远超前者。
java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.putIfAbsent("B", 2); // 不存在才放
map.computeIfPresent("A", (k, v) -> v + 1); // 存在才更新
// 遍历时不会抛 ConcurrentModificationException
map.forEach((k, v) -> System.out.println(k + ": " + v));