Java 并发编程必会的 JUC 辅助类:从 Callable 到阻塞队列

简介: Java 并发编程的 Callable、CountDownLatch、Semaphore、读写锁与阻塞队列,一篇用场景和代码讲透,附四组 API 速查表。

大家好,我是晚安code。

学 Java 并发编程那阵子,我最怕的不是死锁,是 JUC 这堆辅助类的名字:CountDownLatch 和 CyclicBarrier 哪个是减法哪个是加法?Semaphore 到底管什么的?阻塞队列那四个 API 又有什么区别?面试被问一次、背一遍,过两周又混。今天我把它们放进一篇里串起来讲,每个配一段能直接跑的代码和一个生活化场景,再给一张阻塞队列的速查表,看完基本就忘不掉了。示例基于 JDK 8,2026 年 8 月整理,这些类的接口在 JDK 8~21 一直稳定。

先点个收藏,我们开始。

一、Callable:线程也要有返回值

Runnable 最大的遗憾就是 run() 没有返回值——当你需要让线程干完活把结果交回来时,就该上 Callable + FutureTask,这也是面试常问的「Callable 接口用法」。

Callable:Java 提供的带返回值任务接口,泛型 V 就是 call() 方法的返回类型。你可以把它理解成「能领工资的 Runnable」——同样是交给线程跑,但干完活会给你带回一个结果,还允许抛异常。

问题来了:new Thread() 只认 Runnable,接不了 Callable。中间的桥就是 FutureTask:它本身是个 Runnable,同时又能把 Callable 算出的结果存下来,等你去取。

public class CallableTest {
   
    public static void main(String[] args) throws Exception {
   
        FutureTask<String> task = new FutureTask<>(new MyCallable());
        new Thread(task, "线程A").start();
        String result = task.get();   // 没算完会阻塞等待
        System.out.println(result);
    }
}

class MyCallable implements Callable<String> {
   
    @Override
    public String call() {
   
        return "返回结果";   // 这里放耗时业务
    }
}

整条链路是:主线程创建 FutureTask → 子线程执行 call() → 结果存进 FutureTask → 主线程 get() 拿到返回值。看这张时序图,眼睛顺着箭头走一遍就记住了:

Java 并发编程中 Callable 和 FutureTask 拿返回值的时序链路:主线程启动子线程执行 call,get 阻塞等待后拿到结果

这里有两个细节,踩过的都懂:

  1. 同一个 FutureTask 丢给多个线程跑,结果会被缓存。 第二个线程再 get,直接拿缓存返回,不用重新算——我当初以为每个线程都会重跑一遍 call(),其实只有第一个跑。
  2. get() 会阻塞。 我第一次用没注意,直接在业务线程里 task.get(),主线程被卡到怀疑人生。

可能有人会问:futureTask.get() 把主线程卡住了怎么办?

三个办法:把 get 挪到所有线程都启动完的最后再调;用带超时的 get(3, TimeUnit.SECONDS),等不到就放弃;或者把拿结果的动作丢给另一个线程去做,主线程继续干别的。总之别让 get 堵住关键路径。

二、CountDownLatch 和 CyclicBarrier:一个关门,一个发车

CountDownLatch 和 CyclicBarrier 都有计数,但方向完全相反——一个是线程往外走、数到零关门,一个是线程往里聚、凑齐数发车。记住这一句,两者区别就不会再混。

CountDownLatch(倒计时门闩):一个初始计数,每有线程调用 countDown() 计数减一,调用 await() 的主线程一直等到计数归零才继续往下走。你可以把它想成放学锁门——等最后一个同学走出教室,阿姨才落锁。

CountDownLatch latch = new CountDownLatch(6);

for (int i = 0; i < 6; i++) {
   
    new Thread(() -> {
   
        System.out.println(Thread.currentThread().getName() + " 跑路");
        latch.countDown();          // 数量 -1
    }, "同学" + i).start();
}

latch.await();                      // 计数归零才放行
System.out.println("关门");

跑这段代码,你会看到 6 个同学依次「跑路」,然后主线程才被唤醒,最后一行才打印「关门」——注意「关门」永远是最后出现的:

CyclicBarrier(循环栅栏):N 个线程互相等,全部调用 await() 到达屏障后,才一起放行,同时执行一个可选的集合任务。你可以把它想成集齐七颗龙珠召唤神龙——凑不齐,谁也别想先走。

CyclicBarrier barrier = new CyclicBarrier(7, () -> {
   
    System.out.println("神龙召唤!");
});

for (int i = 1; i <= 7; i++) {
   
    final int temp = i;
    new Thread(() -> {
   
        System.out.println("收集到第 " + temp + " 颗龙珠");
        try {
   
            barrier.await();        // 等齐 7 个线程
        } catch (Exception e) {
   
            e.printStackTrace();
        }
    }).start();
}

两者都是「等够数就放行」,区别就在谁等、放行之后干什么。这张对比图一眼看清:

CountDownLatch 与 CyclicBarrier 对比:一个倒计时关门,一个集齐发车

一句话记住:CountDownLatch 是减法,数完任务执行完,主线程最后收尾;CyclicBarrier 是加法(集合),人齐了大家一起开工。

三、Semaphore:停车场里练限流

Semaphore 是 JUC 里最像限流器的工具——许可数量固定,抢到就干,抢不到就排队,release 一个让一个进来。做并发限流、控制最大线程数时它都是首选。

Semaphore(信号量):维护 N 个许可证,acquire() 拿一个、release() 还一个,没许可证就阻塞等待。你可以把它想成停车场——一共就 2 个车位,车满了,后来的只能停在外面等位。

Semaphore semaphore = new Semaphore(2);   // 2 个车位

for (int i = 0; i < 6; i++) {
   
    new Thread(() -> {
   
        try {
   
            semaphore.acquire();          // 抢到车位
            System.out.println(Thread.currentThread().getName() + " 进停车场");
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        } finally {
   
            semaphore.release();          // 释放车位
        }
    }, "车" + i).start();
}

acquire() 拿走一个许可,release() 还回来并唤醒一个等待线程。用在哪?数据库连接池、接口限流、控制并发数,都是它的主场——多个共享资源互斥使用,就是信号量最典型的作用。

Semaphore 用固定许可数限流,2 个车位放行 2 辆车。

四、读写锁:读多写少的「分工授权」

普通的锁一视同仁,读和写互相挤;读写锁把「读」和「写」分开授权——读可以一起读,写必须独占。读写锁就是读多写少场景的最优解。

ReentrantReadWriteLock(读写锁):内部维护一把读锁(共享锁)和一把写锁(独占锁)。读锁可被多个线程同时持有,写锁同一时刻只能一个线程持有。你可以把它想成图书馆——读者可以好多人一起看书,但有人要改书架,其他人就得先让开。

三种组合的共存关系,一张表记住:

线程组合 能否共存 原因
读-读 ✅ 共存 共享锁,谁读都不影响谁
读-写 ❌ 互斥 写着读着会读到半成品
写-写 ❌ 互斥 独占锁,同时写必然打架

一句话:读读共存、读写互斥、写写互斥。用它包一层缓存,比给整个 HashMap 上普通锁高效得多:

class MyCacheLock {
   
    private final Map<String, Object> map = new HashMap<>();
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
   
        lock.writeLock().lock();
        try {
    map.put(key, value); }
        finally {
    lock.writeLock().unlock(); }
    }

    public Object get(String key) {
   
        lock.readLock().lock();
        try {
    return map.get(key); }
        finally {
    lock.readLock().unlock(); }
    }
}

不加锁的 HashMap 缓存,写的同时另一个线程在读,轻则读到脏数据,重则在并发 put 时 key 值丢失甚至死循环。读写锁就是专门补这个口子的。

五、阻塞队列:四组 API 一张表记牢

阻塞队列四组 API 是最容易被面试问倒的细节——add、offer、put、offer(超时) 四种添加方式,区别全在「满了怎么办」,一张表就能记牢。

BlockingQueue(阻塞队列):线程安全且自带阻塞能力的队列,放不下时 put 会阻塞,取不到时 take 会阻塞。你可以把它想成食堂打饭窗口——窗口空了厨师多做几个等着,窗口满了顾客就在队伍里排队等。

阻塞队列用在哪?最常见的就是多线程协作、线程池里塞任务的地方——生产消费两边节奏对不上,全靠它兜住。它解决的就是生产者与消费者之间的「节奏不同步」。

四组 API 对照表(背这一张就够):

方式 满了/空了直接报错 满了/空了返回特殊值 满了/空了阻塞等待 满了/空了超时放弃
添加 add offer put offer(值, 超时, 单位)
移除 remove poll take poll(超时, 单位)
探测队首 element peek

add 满了抛 IllegalStateException,offer 满了返回 false,put 满了一直堵着,offer(超时) 堵几秒就放弃。以 ArrayBlockingQueue 为例,队列容量 3:

ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

queue.put("a");
queue.put("b");
queue.put("c");
// queue.put("d");          // 满了,这里会一直阻塞

System.out.println(queue.take());   // 取走一个,腾出位置
System.out.println(queue.take());
System.out.println(queue.take());

这套四组 API 我当年背了 N 遍还是混,直到自己画了上面这张表才算彻底记住。

生产者、队列缓冲、消费者,各干各的,节奏不同步也不怕。

六、SynchronousQueue:一个不存元素的队列

SynchronousQueue 是全 JUC 里最特别的队列——它没有容量,put 一个元素必须等别人 take 走,否则永远堵着,它实现的是线程间的「手递手」。

SynchronousQueue(同步队列):零容量的阻塞队列,生产者 put 一个元素必须等消费者 take 走,消费者 take 也必须等生产者 put,两边一对一交接。你可以把它想成高空抛接——一个人扔,另一个人必须正好接住,中间没有筐。

BlockingQueue<String> queue = new SynchronousQueue<>();

new Thread(() -> {
                          // 生产者 T1
    try {
   
        queue.put("1");
        System.out.println("T1 放 1,没人接,堵住了");
    } catch (InterruptedException e) {
   
        e.printStackTrace();
    }
}).start();

new Thread(() -> {
                          // 消费者 T2
    try {
   
        TimeUnit.SECONDS.sleep(3);       // 3 秒后才来接
        System.out.println("T2 取到:" + queue.take());
    } catch (InterruptedException e) {
   
        e.printStackTrace();
    }
}).start();

跑起来你会看到:T1 放 1 之后因为没人 take 而一直堵着,直到 3 秒后 T2 把它取走,两边才继续。别的队列靠「存」缓冲,它靠「等」直接交接。

零容量的队列,put 和 take 必须同时在线,中间没有缓冲。

可能有人会问:SynchronousQueue 又不能存元素,到底有什么用?

它的价值恰恰在「不存」——天然实现一对一手递手,没有中间堆积,生产消费严格同步。线程池的 Executors.newCachedThreadPool 内部就用它做任务队列,来一个任务就得有线程来接,没有人接就新建线程,所以它的线程数才会跟着任务数一起涨。

七、写在最后

做 Java 并发编程,与其背类名,不如记场景——CountDownLatch 管「凑齐就放行」,CyclicBarrier 管「凑齐才发车」,Semaphore 管「限流放行」,读写锁管「分工授权」,阻塞队列管「缓冲排队」,SynchronousQueue 管「一对一交接」。想清楚每个类让线程「等什么、怎么放行」,名字自然就记住了。

这几个辅助类串起来,正好是线程池和生产者-消费者模型落地前的完整拼图,下一篇可以展开线程池的七大参数怎么配。说白了就一句话:记场景,不背类名。


我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你学 Java 并发编程时最容易记混的是哪两个类?有没有被 futureTask.get() 的阻塞坑过?

目录
相关文章
|
5天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1574 112
|
12天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1941 8
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
6天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
|
6天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
528 112
|
18天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
2567 4
|
10天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
721 111
|
20天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2636 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
6天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
7天前
Qoder 一周年 × Qwen3.8-Max 正式上线,多重好礼限时领
8月3日,Qwen3.8-Max 正式上线Qoder,迎来Qoder一周年。新老用户可领800次免费调用,下单再赠2000次;夜间(22:00–08:00)调用5折;邀请好友双方得积分与调用额度。
448 1

热门文章

最新文章