少女祈祷中...
NEKO
知识库

java 并发工具面试要点

一、CountDownLatch

Q1:什么是 CountDownLatch?

答: CountDownLatch 是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。

核心机制:

  • 内部维护一个计数器(基于 AQS 的 state)

  • countDown():计数器减 1

  • await():阻塞直到计数器为 0

  • 计数器归零后,不可重置

CountDownLatch latch = new CountDownLatch(3);

// 工作线程
new Thread(() -> {
    doWork();
    latch.countDown(); // 完成一个,减 1
}).start();

// 主线程
latch.await(); // 等待所有工作线程完成
System.out.println("全部完成");

Q2:CountDownLatch 的底层原理是什么?

答: 基于 AQS 共享模式:

public class CountDownLatch {
    private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            setState(count); // 初始计数
        }

        // 共享模式获取:state == 0 才成功
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        // 共享模式释放:CAS 减 1
        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int c = getState();
                if (c == 0) return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
}

要点:

  • await() → acquireSharedInterruptibly(1)

  • countDown() → releaseShared(1)

  • state 为 0 时,唤醒所有等待线程


Q3:CountDownLatch 和 join() 有什么区别?

答:

对比项

CountDownLatch

join()

粒度

可多个线程、多次

一个线程

灵活性

可在任意点 countDown

必须等线程结束

复用

不可重置

一次性

组合

可与其他工具配合

单独使用

// join:等线程结束
Thread t = new Thread(() -> doWork());
t.start();
t.join();

// CountDownLatch:等任务完成
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
    doWork();
    latch.countDown();
}).start();
latch.await();

优势:CountDownLatch 更灵活,不必等线程结束。


Q4:CountDownLatch 能重复使用吗?

答: 不能。计数器归零后无法重置:

CountDownLatch latch = new CountDownLatch(1);
latch.countDown();
latch.await(); // 立即返回
latch.countDown(); // 无效果,state 已为 0

需要重置:用 CyclicBarrier。


Q5:CountDownLatch 的典型应用场景?

答:

  1. 等待多个任务完成:

CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        doWork();
        latch.countDown();
    }).start();
}
latch.await();
  1. 并发测试:模拟高并发

CountDownLatch start = new CountDownLatch(1);
for (int i = 0; i < 100; i++) {
    new Thread(() -> {
        start.await(); // 同时起跑
        doRequest();
    }).start();
}
start.countDown();
  1. 多阶段任务:等待初始化完成


Q6:CountDownLatch 的 await() 有超时版本吗?

答: 有:

boolean completed = latch.await(3, TimeUnit.SECONDS);
if (!completed) {
    // 超时,做降级处理
}

注意:超时后不会自动取消其他线程,需要自行处理。


二、CyclicBarrier

Q7:什么是 CyclicBarrier?

答: CyclicBarrier 让一组线程互相等待,直到所有线程都到达屏障点,再一起继续。

核心机制:

  • 内部维护一个计数器(基于 ReentrantLock + Condition)

  • await():到达屏障,阻塞

  • 所有线程到达后,计数器重置,唤醒所有线程

  • 可循环使用

CyclicBarrier barrier = new CyclicBarrier(3, () -> 
    System.out.println("所有线程到达"));

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println("到达屏障");
        barrier.await(); // 互相等待
        System.out.println("继续执行");
    }).start();
}

Q8:CyclicBarrier 和 CountDownLatch 有什么区别?

答: 常考重点:

对比项

CountDownLatch

CyclicBarrier

方向

一个线程等多个线程

多个线程互相等

复用

不可重置

可循环使用

计数

countDown() 减

await() 减

触发

计数为 0 唤醒 await

计数为 0 唤醒所有

回调

无

有 barrierAction

底层

AQS 共享模式

ReentrantLock + Condition

异常

不抛

抛 BrokenBarrierException

// CountDownLatch:主线程等子线程
latch.await();

// CyclicBarrier:子线程互相等
barrier.await();

记忆:

  • CountDownLatch 是"等别人"

  • CyclicBarrier 是"互相等"


Q9:CyclicBarrier 的底层原理是什么?

答: 基于 ReentrantLock + Condition:

public class CyclicBarrier {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition trip = lock.newCondition();
    private final int parties;      // 参与线程数
    private int count;              // 剩余未到达数
    private Generation generation;  // 代,用于重置

    public int await() {
        lock.lock();
        try {
            int index = --count;
            if (index == 0) {
                // 最后一个到达,执行回调,唤醒所有
                nextGeneration();
                return 0;
            }
            // 未到齐,等待
            trip.await();
            return index;
        } finally {
            lock.unlock();
        }
    }
}

要点:

  • 用 count 记录剩余未到达线程

  • 最后一个到达时,调用 nextGeneration() 重置并唤醒

  • Generation 用于区分"代",支持循环


Q10:CyclicBarrier 的 barrierAction 是什么?

答: 当所有线程到达屏障时,由最后一个到达的线程执行:

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达,执行汇总");
    // 汇总数据、合并结果
});

执行时机:

  • 所有线程调用 await() 后

  • 在唤醒其他线程之前

  • 由最后一个到达的线程执行

用途:合并结果、记录日志、更新状态。


Q11:CyclicBarrier 如何实现循环复用?

答: 通过 nextGeneration() 重置:

private void nextGeneration() {
    trip.signalAll();   // 唤醒所有等待线程
    count = parties;    // 重置计数
    generation = new Generation(); // 新一代
}

循环示例:

CyclicBarrier barrier = new CyclicBarrier(3);

for (int round = 0; round < 3; round++) {
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            doWork();
            barrier.await(); // 每轮都等待
        }).start();
    }
}

关键:count 重置为 parties,generation 更新。


Q12:CyclicBarrier 的 BrokenBarrierException 是什么?

答: 当屏障被打破时抛出:

触发场景:

  1. 某个线程 await() 时被中断

  2. 某个线程 await() 超时

  3. 调用 reset() 时其他线程正在等待

try {
    barrier.await();
} catch (BrokenBarrierException e) {
    // 屏障已破坏,需重置
}

处理:

  • 调用 barrier.reset() 重置

  • 或重新创建 CyclicBarrier


Q13:CyclicBarrier 的 reset() 有什么作用?

答: 重置屏障到初始状态:

barrier.reset();
// 当前等待的线程抛 BrokenBarrierException
// 计数器重置为 parties

注意:

  • 正在等待的线程会收到异常

  • 用于取消当前轮次

  • 慎用,可能导致线程异常


Q14:CyclicBarrier 的典型应用场景?

答:

  1. 多阶段计算:

// 阶段1:所有线程计算
// 屏障
// 阶段2:所有线程汇总
  1. 并行迭代:

for (int i = 0; i < rounds; i++) {
    parallelWork();
    barrier.await();
    mergeResults();
}
  1. 游戏加载:所有玩家加载完成才开始


三、Semaphore

Q15:什么是 Semaphore?

答: Semaphore(信号量)用于控制同时访问特定资源的线程数量。

核心机制:

  • 内部维护许可数(permits)

  • acquire():获取许可,无许可则阻塞

  • release():释放许可

  • 基于 AQS 共享模式

Semaphore semaphore = new Semaphore(3); // 最多 3 个并发

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        semaphore.acquire(); // 获取许可
        try {
            doWork(); // 最多 3 个线程同时执行
        } finally {
            semaphore.release(); // 释放许可
        }
    }).start();
}

Q16:Semaphore 的底层原理是什么?

答: 基于 AQS 共享模式,state 表示可用许可数:

public class Semaphore {
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 获取许可:state 减
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

        // 释放许可:state 加
        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (compareAndSetState(current, next))
                    return true;
            }
        }
    }
}

要点:

  • acquire() → acquireSharedInterruptibly(1)

  • release() → releaseShared(1)

  • state < 0 时阻塞


Q17:Semaphore 的公平模式和非公平模式有什么区别?

答:

对比项

公平模式

非公平模式

获取顺序

FIFO

允许插队

吞吐量

低

高

饥饿

不会

可能

默认

否

是

Semaphore fair = new Semaphore(3, true);   // 公平
Semaphore unfair = new Semaphore(3, false); // 非公平(默认)

非公平:新线程直接 CAS 抢许可,抢不到才排队。


Q18:Semaphore 的 acquire() 和 release() 必须配对吗?

答: 必须配对,否则许可泄漏:

semaphore.acquire();
try {
    doWork();
} finally {
    semaphore.release(); // 必须释放
}

泄漏后果:

  • 许可越来越少

  • 最终无许可可用,线程永久阻塞

注意:release() 可以增加许可数,甚至超过初始值。


Q19:Semaphore 能实现互斥锁吗?

答: 能。许可数为 1 时,等价于互斥锁:

Semaphore mutex = new Semaphore(1);

mutex.acquire();
try {
    // 临界区
} finally {
    mutex.release();
}

和 ReentrantLock 的区别:

  • Semaphore 不区分持有者

  • 不可重入

  • 更灵活(可释放多个许可)


Q20:Semaphore 的 tryAcquire() 有什么用?

答: 非阻塞获取许可:

// 立即返回
if (semaphore.tryAcquire()) {
    try { doWork(); }
    finally { semaphore.release(); }
} else {
    // 获取失败,做其他事
}

// 带超时
if (semaphore.tryAcquire(1, TimeUnit.SECONDS)) {
    try { doWork(); }
    finally { semaphore.release(); }
}

用途:限流、降级、避免阻塞。


Q21:Semaphore 和 ThreadPoolExecutor 有什么区别?

答:

对比项

Semaphore

线程池

控制对象

并发访问数

线程数

线程创建

不限

复用

阻塞点

acquire()

队列满

适用

资源限流

任务执行

// Semaphore:限制并发访问数据库
Semaphore dbLimit = new Semaphore(10);

// 线程池:限制线程数
ExecutorService pool = new ThreadPoolExecutor(...);

配合使用:线程池 + Semaphore 限流。


Q22:Semaphore 的典型应用场景?

答:

  1. 限流:

Semaphore limit = new Semaphore(100);
limit.acquire();
try { handleRequest(); }
finally { limit.release(); }
  1. 资源池:数据库连接池、对象池

  2. 互斥:许可为 1

  3. 控制并发度:限制同时执行的任务数


四、Exchanger

Q23:什么是 Exchanger?

答: Exchanger 用于两个线程之间交换数据:

  • 线程 A 调用 exchange(dataA),阻塞

  • 线程 B 调用 exchange(dataB),阻塞

  • 两者都到达后,交换数据,继续执行

Exchanger<String> exchanger = new Exchanger<>();

// 线程 A
new Thread(() -> {
    String data = "A的数据";
    String received = exchanger.exchange(data);
    System.out.println("A 收到: " + received);
}).start();

// 线程 B
new Thread(() -> {
    String data = "B的数据";
    String received = exchanger.exchange(data);
    System.out.println("B 收到: " + received);
}).start();

输出:

A 收到: B的数据
B 收到: A的数据

Q24:Exchanger 的底层原理是什么?

答: 基于 CAS + 等待队列:

  • 内部维护一个 Participant 槽位

  • 第一个线程到达,把自己的数据放入槽位,等待

  • 第二个线程到达,取出数据,放入自己的数据,唤醒第一个线程

  • 两者交换完成

// 简化逻辑
public V exchange(V x) {
    // 1. 尝试把 x 放入槽位
    // 2. 如果有等待线程,交换并唤醒
    // 3. 否则自己等待
}

特点:

  • 只支持两个线程交换

  • 多线程需多次配对


Q25:Exchanger 的 exchange() 有超时版本吗?

答: 有:

try {
    String received = exchanger.exchange(data, 
        3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    // 超时,对方未到达
}

超时后:当前线程继续执行,但数据未交换。


Q26:Exchanger 支持多个线程吗?

答: 不直接支持。Exchanger 设计为两两交换:

  • 多个线程调用 exchange(),会两两配对

  • 配对顺序不确定

  • 适合生产者-消费者、遗传算法等场景

// 4 个线程,会两两交换
for (int i = 0; i < 4; i++) {
    new Thread(() -> {
        exchanger.exchange(data);
    }).start();
}

Q27:Exchanger 的典型应用场景?

答:

  1. 生产者-消费者:

// 生产者
Data data = produce();
Data received = exchanger.exchange(data);

// 消费者
Data data = consume();
Data received = exchanger.exchange(data);
  1. 遗传算法:交换基因

  2. 管道处理:两个阶段交换数据

  3. 校对:两个线程交换结果比对


Q28:Exchanger 和 SynchronousQueue 有什么区别?

答:

对比项

Exchanger

SynchronousQueue

方向

双向交换

单向传递

线程数

2 个

多对多

数据

双方都有数据

一方生产,一方消费

阻塞

exchange()

put()/take()

// Exchanger:双向
String a = exchanger.exchange("A");
String b = exchanger.exchange("B");

// SynchronousQueue:单向
queue.put("data");
String s = queue.take();

五、综合对比

Q29:四大并发工具的对比?

答:

工具

作用

线程数

复用

底层

CountDownLatch

一个等多个

N+1

否

AQS 共享

CyclicBarrier

多个互相等

N

是

Lock + Condition

Semaphore

控制并发数

N

是

AQS 共享

Exchanger

两两交换

2

是

CAS + 队列


Q30:如何选择并发工具?

答:

场景

工具

主线程等子任务

CountDownLatch

多阶段并行

CyclicBarrier

限流

Semaphore

两线程交换数据

Exchanger

简单计数

AtomicInteger

任务执行

线程池

记忆:

  • 等别人 → CountDownLatch

  • 互相等 → CyclicBarrier

  • 限并发 → Semaphore

  • 换数据 → Exchanger


Q31:CountDownLatch 和 CyclicBarrier 能互相替代吗?

答: 不能完全替代:

  • CountDownLatch:一个线程等多个,不可复用

  • CyclicBarrier:多个线程互相等,可复用

场景区分:

// 主线程等 3 个子线程 → CountDownLatch
// 3 个子线程互相等 → CyclicBarrier

但可以用 CyclicBarrier 模拟 CountDownLatch:

CyclicBarrier barrier = new CyclicBarrier(N + 1);
// 子线程 barrier.await()
// 主线程 barrier.await()

Q32:Semaphore 能实现 CyclicBarrier 吗?

答: 可以,但麻烦:

// 用 Semaphore 模拟屏障
Semaphore semaphore = new Semaphore(0);
int parties = 3;

// 每个线程
semaphore.release();          // 到达
semaphore.acquire(parties);   // 等待所有到达

不推荐:CyclicBarrier 更语义化、更简洁。


Q33:这些工具的底层都是 AQS 吗?

答: 不全是:

工具

底层

CountDownLatch

AQS 共享模式

Semaphore

AQS 共享模式

CyclicBarrier

ReentrantLock + Condition

Exchanger

CAS + 等待队列

注意:CyclicBarrier 用的是 Lock,不是直接继承 AQS。


Q34:这些工具是线程安全的吗?

答: 都是线程安全的:

  • CountDownLatch:AQS 保证

  • CyclicBarrier:ReentrantLock 保证

  • Semaphore:AQS 保证

  • Exchanger:CAS 保证

但使用时注意:

  • Semaphore 的 acquire/release 要配对

  • CyclicBarrier 异常后要 reset


Q35:如何在生产中监控这些工具?

答:

  1. CountDownLatch:

latch.getCount(); // 剩余计数
  1. CyclicBarrier:

barrier.getNumberWaiting(); // 等待线程数
barrier.getParties();       // 总参与数
barrier.isBroken();         // 是否损坏
  1. Semaphore:

semaphore.availablePermits(); // 可用许可
semaphore.getQueueLength();   // 等待队列长度
  1. Exchanger:无直接监控方法


六、实战与踩坑

Q36:CountDownLatch 的 countDown() 忘记调用会怎样?

答: await() 会永久阻塞:

CountDownLatch latch = new CountDownLatch(3);
// 只 countDown 2 次
latch.await(); // 永远阻塞

解决:

  • 用超时 await(timeout)

  • 确保 finally 中 countDown

  • 监控计数


Q37:CyclicBarrier 的 await() 抛异常后怎么办?

答:

try {
    barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
    barrier.reset(); // 重置屏障
    // 或重新创建
}

注意:异常后屏障损坏,其他等待线程也会抛异常。


Q38:Semaphore 的 release() 能超过初始许可吗?

答: 能。Semaphore 不检查上限:

Semaphore sem = new Semaphore(1);
sem.release(); // 许可变 2
sem.release(); // 许可变 3

风险:

  • 破坏限流语义

  • 需自行保证 acquire/release 配对


Q39:CyclicBarrier 的 barrierAction 抛异常会怎样?

答: 屏障被打破:

  • 其他等待线程抛 BrokenBarrierException

  • 屏障不可用,需 reset

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    throw new RuntimeException("汇总失败");
});

建议:barrierAction 内 try-catch,避免影响屏障。


Q40:如何在并发测试中使用 CountDownLatch?

答: 模拟高并发:

int threadCount = 100;
CountDownLatch start = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(threadCount);

for (int i = 0; i < threadCount; i++) {
    new Thread(() -> {
        try {
            start.await(); // 同时起跑
            doRequest();
        } finally {
            end.countDown();
        }
    }).start();
}

start.countDown(); // 发令
end.await();       // 等所有完成

效果:100 个线程同时发起请求,模拟真实并发。

评论