Z
ZHANK
编程在线教程Java 教程线程池与并发工具
多线程

线程池与并发工具

使用 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() // 拒绝策略:队列满了让主线程跑
);

参数执行逻辑:

  1. 任务来了 → 核心线程有空闲?直接给
  2. 核心线程都忙 → 放入队列
  3. 队列也满了 → 创建新线程(直到 maxPoolSize)
  4. 线程也满了 → 执行拒绝策略

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

HashtableCollections.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));