Skip to content

并发工具类简介

概述

java.util.concurrent(常称 JUC)是 JDK 提供的并发工具包,在 synchronizedLock 之外,提供了更丰富的线程协调与线程安全能力。本文介绍三类常用内容:同步器(CountDownLatch、CyclicBarrier、Semaphore)、原子类(AtomicInteger 等)以及并发集合的简要概念,帮助你在 线程池 等场景下正确选用工具,写出可靠的多线程代码。


JUC 包概览

类别代表类典型用途
同步器CountDownLatch、CyclicBarrier、Semaphore、Phaser多线程之间的「等待/放行」协调
原子类AtomicInteger、AtomicLong、AtomicReference无锁的线程安全计数、引用更新
锁与条件ReentrantLock、ReentrantReadWriteLock显式锁(详见 同步与锁
并发集合ConcurrentHashMap、BlockingQueue、CopyOnWriteArrayList线程安全的容器,替代同步包装
执行框架ExecutorService、ThreadPoolExecutor线程池(详见 线程池

下面重点介绍同步器原子类的入门用法;并发集合仅做简要说明,便于你在实际项目中知道「该用哪一类」。


CountDownLatch:一次性倒计时

CountDownLatch 用于让一个或多个线程等待其它线程完成一定次数(N 次)的操作。内部维护一个计数器,构造时指定 N;其它线程每完成一次就调用 countDown() 将计数减 1;等待的线程通过 await() 阻塞,直到计数变为 0 后一起继续。

典型场景:主线程等待所有子任务完成后再做汇总、发令「所有运动员就位后同时开跑」等。

基本用法

java
// 构造:计数为 N
CountDownLatch latch = new CountDownLatch(N);

// 在「完成任务」的线程中:每完成一次就减 1
latch.countDown();

// 在「等待」的线程中:阻塞直到计数为 0
latch.await();           // 可被中断,抛出 InterruptedException
latch.await(2, TimeUnit.SECONDS);  // 带超时,超时后不再等待

示例:主线程等待多个子任务完成

java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int workerCount = 3;
        CountDownLatch latch = new CountDownLatch(workerCount);

        for (int i = 0; i < workerCount; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    System.out.println("Worker " + id + " 开始执行");
                    Thread.sleep(500 + id * 100L);
                    System.out.println("Worker " + id + " 完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();  // 无论如何都减 1,避免主线程永远等不到
                }
            }).start();
        }

        System.out.println("主线程等待所有 Worker 完成...");
        latch.await();
        System.out.println("所有 Worker 已完成,主线程继续");
    }
}

提示

finally 中调用 countDown(),可以保证即使任务抛异常或提前 return,计数也会减 1,避免等待线程永远阻塞。


CyclicBarrier:可复用的栅栏

CyclicBarrier 让一组线程在某个「栅栏」处互相等待,直到所有线程都到达后,再一起继续执行。与 CountDownLatch 不同:CountDownLatch 是「一个减计数,其它人等」;CyclicBarrier 是「所有人到齐才一起走」,且可重复使用(下一轮再次 await 即可)。

典型场景:多轮迭代计算中,每轮都要等所有线程算完再进入下一轮;多人游戏中「准备就绪后同时开始」。

基本用法

java
// 构造:参与线程数 N,可选一个「到齐后」执行的动作
CyclicBarrier barrier = new CyclicBarrier(N);
CyclicBarrier barrierWithAction = new CyclicBarrier(N, () -> System.out.println("全部到齐,开始下一阶段"));

// 每个参与线程到达栅栏时调用
barrier.await();           // 阻塞直到 N 个线程都调用了 await
barrier.await(2, TimeUnit.SECONDS);  // 带超时

示例:多线程分阶段协作

java
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int parties = 3;
        CyclicBarrier barrier = new CyclicBarrier(parties, () -> System.out.println(">>> 本阶段所有人到齐,继续下一阶段"));

        for (int i = 0; i < parties; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    System.out.println("线程 " + id + " 完成阶段一");
                    barrier.await();
                    System.out.println("线程 " + id + " 完成阶段二");
                    barrier.await();
                    System.out.println("线程 " + id + " 全部完成");
                } catch (InterruptedException | BrokenBarrierException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

注意

若有一个线程在 await() 时被中断或超时,或栅栏被 reset(),其它等待中的线程会收到 BrokenBarrierException,表示本轮栅栏已失效。使用带超时的 await 时要注意处理该异常并决定是否重试或退出。


Semaphore:信号量(限制并发数)

Semaphore 维护一组许可(permits)数量,线程在执行前先 acquire 获取许可(没有则阻塞),用完后 release 释放许可,从而限制同时执行某段代码的线程数

典型场景:限制同时访问某资源的线程数(如数据库连接池、限流)、控制「最多 N 个线程进入临界区」。

基本用法

java
// 构造:许可数量
Semaphore sem = new Semaphore(2);   // 最多 2 个线程同时持有许可

sem.acquire();    // 获取 1 个许可,没有则阻塞;可被中断
sem.release();    // 释放 1 个许可

sem.tryAcquire();                    // 非阻塞,获取到返回 true
sem.tryAcquire(1, TimeUnit.SECONDS); // 等待最多 1 秒

示例:限制同时执行的任务数

java
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 最多 2 个线程同时进入「使用资源」的逻辑
        Semaphore sem = new Semaphore(2);

        for (int i = 0; i < 5; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    sem.acquire();
                    System.out.println("线程 " + id + " 获得许可,使用资源");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    sem.release();
                    System.out.println("线程 " + id + " 释放许可");
                }
            }).start();
        }
    }
}

提示

release() 建议放在 finally 中,确保无论是否抛异常都会释放许可,避免许可泄漏导致后续线程永远拿不到许可。


原子类简介

当只需要对单个变量做线程安全的「读-改-写」(如自增、CAS 更新)时,不必加锁,可使用 java.util.concurrent.atomic 包下的原子类,底层基于 CAS(Compare-And-Swap) 实现无锁更新。

说明
AtomicInteger、AtomicLong原子整型、长整型,提供 incrementAndGet()addAndGet()compareAndSet()
AtomicReference<V>原子更新引用
AtomicBoolean原子布尔

示例:用 AtomicInteger 替代同步计数

java
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    private static final AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) counter.incrementAndGet(); });
        Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) counter.incrementAndGet(); });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " + counter.get());  // 稳定输出 20000
    }
}

在「高并发、简单计数或简单状态更新」场景下,原子类通常比 synchronizedLock 更轻量,且能避免死锁;复杂复合操作仍需要锁或更高级的并发结构。


并发集合简要说明

JUC 还提供了一系列线程安全的集合,在多线程环境下可直接使用,而无需在外层再加 synchronizedLock

类型代表类简要说明
MapConcurrentHashMap高并发下替代 Hashtable / 同步的 HashMap,分段或 CAS 优化
QueueBlockingQueue(如 LinkedBlockingQueue、ArrayBlockingQueue)阻塞队列,支持「队列空时取阻塞、满时放阻塞」,常与 线程池 和生产者-消费者配合
ListCopyOnWriteArrayList写时复制,读多写少时线程安全且读无锁

具体 API 与选用场景可在学习完 集合 后,结合「多线程共享容器」的需求查阅官方文档或进阶资料。


注意事项

注意

  • CountDownLatch 的计数只能减不能增,且不能重置,属于一次性使用;若需要多轮「等待 N 次完成」,应使用 CyclicBarrier 或新建多个 CountDownLatch。
  • await() 会响应中断:当前线程在等待时若被中断,会抛出 InterruptedException。捕获后通常应恢复中断状态:Thread.currentThread().interrupt(),由上层决定是否退出。

提示

  • 同步器与 线程池 结合使用时,在任务中调用 latch.countDown()barrier.await() 即可,无需再「new Thread」;线程池负责线程复用与调度。
  • 需要「多线程等一个结果」时用 CountDownLatch;需要「多线程互相等,到齐再走」且可能多轮时用 CyclicBarrier;需要「限制同时执行的线程数」时用 Semaphore

相关链接

基于 VitePress 构建