JUC高并发工具类实战指南:从入门到避坑(Spring Boot + Java)

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

在高并发场景下,Java原生的 synchronizedvolatile 虽然能解决部分问题,但面对复杂的并发控制(比如限流、倒计时、线程协作等),就显得力不从心了。这时,JUC(java.util.concurrent)包中的高并发工具类就成了我们的“神器”。

本文将结合 Spring Boot 项目实战,用通俗易懂的方式讲解几个核心 JUC 工具类,并附上正例、反例、注意事项,让小白也能快速上手!


🎯 需求场景

假设你正在开发一个电商秒杀系统:

  • 用户点击“抢购”按钮后,多个请求同时涌入;
  • 系统需要限制最多 100 个用户成功下单(限流);
  • 所有请求需等待库存初始化完成才能开始处理(线程同步);
  • 抢购结束后要汇总结果(线程协作)。

这些需求,用传统方式很难优雅实现,而 JUC 工具类可以轻松搞定!


一、CountDownLatch:倒计时门闩

✅ 作用

让一个或多个线程等待其他线程完成操作后再继续执行。

🔧 典型场景

  • 主线程等待多个子任务完成后再汇总结果;
  • 系统启动时等待多个服务初始化完毕。

💡 Spring Boot 正例代码

@RestController
public class CountDownLatchController {

    @GetMapping("/init")
    public String initSystem() throws InterruptedException {
        int serviceCount = 3;
        CountDownLatch latch = new CountDownLatch(serviceCount);

        // 模拟3个服务初始化
        for (int i = 1; i <= serviceCount; i++) {
            final int serviceId = i;
            new Thread(() -> {
                try {
                    System.out.println("服务 " + serviceId + " 开始初始化...");
                    Thread.sleep(2000); // 模拟耗时
                    System.out.println("服务 " + serviceId + " 初始化完成!");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown(); // 计数减1
                }
            }).start();
        }

        latch.await(); // 主线程阻塞,直到计数归零
        return "所有服务初始化完成,系统启动成功!";
    }
}

❌ 反例(常见错误)

// 错误:没有在 finally 中调用 countDown()
new Thread(() -> {
    if (someCondition) {
        throw new RuntimeException("初始化失败");
    }
    latch.countDown(); // 如果异常抛出,这行不会执行!
}).start();

后果latch.await() 永远阻塞,程序卡死!

⚠️ 注意事项

  • countDown() 必须放在 finally 块中,确保异常也能释放;
  • await() 可设置超时时间:latch.await(5, TimeUnit.SECONDS),避免永久阻塞。

二、Semaphore:信号量(限流神器)

✅ 作用

控制同时访问特定资源的线程数量,常用于限流

🔧 典型场景

  • 数据库连接池(最多10个连接);
  • 秒杀系统限制并发用户数。

💡 Spring Boot 正例代码(模拟秒杀限流)

@Component
public class SeckillService {

    // 最多允许10个用户同时抢购
    private final Semaphore semaphore = new Semaphore(10);

    public String seckill(Long userId) {
        boolean acquired = false;
        try {
            // 尝试获取许可(非阻塞)
            if (semaphore.tryAcquire(1, TimeUnit.SECONDS)) {
                acquired = true;
                // 模拟抢购逻辑
                Thread.sleep(500);
                return "用户 " + userId + " 抢购成功!";
            } else {
                return "用户 " + userId + " 抢购失败:系统繁忙,请稍后再试";
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return "用户 " + userId + " 抢购中断";
        } finally {
            if (acquired) {
                semaphore.release(); // 一定要释放!
            }
        }
    }
}

@RestController
public class SeckillController {

    @Autowired
    private SeckillService seckillService;

    @GetMapping("/seckill")
    public String seckill(@RequestParam Long userId) {
        return seckillService.seckill(userId);
    }
}

❌ 反例

// 错误:忘记 release()
if (semaphore.tryAcquire()) {
    // 处理业务...
    // 忘记 semaphore.release();
}

后果:许可被永久占用,后续所有请求都被拒绝!

⚠️ 注意事项

  • release() 必须在 finally 中调用;
  • 使用 tryAcquire(timeout) 避免无限等待;
  • 信号量是公平/非公平可选的,高并发下通常用非公平(性能更好)。

三、CyclicBarrier:循环栅栏(线程协作)

✅ 作用

让一组线程互相等待,直到所有线程都到达某个屏障点,再一起继续执行。可重复使用(与 CountDownLatch 的关键区别)。

🔧 典型场景

  • 多线程分片计算,每轮计算完成后汇总;
  • 游戏房间:等所有玩家准备就绪才开始。

💡 Spring Boot 正例(多线程分片计算)

@Service
public class CyclicBarrierService {

    public void runBatchTask() {
        int threadCount = 4;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("【汇总阶段】所有线程已完成本轮任务,开始汇总...");
        });

        for (int i = 0; i < threadCount; i++) {
            final int taskId = i;
            new Thread(() -> {
                try {
                    // 第一轮任务
                    System.out.println("线程 " + taskId + " 执行任务 A");
                    Thread.sleep(1000);
                    barrier.await(); // 等待其他线程

                    // 第二轮任务
                    System.out.println("线程 " + taskId + " 执行任务 B");
                    Thread.sleep(1000);
                    barrier.await(); // 再次等待(可重用!)

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

❌ 反例

// 错误:线程数与 CyclicBarrier 构造参数不一致
CyclicBarrier barrier = new CyclicBarrier(3);
// 但只启动了2个线程 → 永远无法触发 barrier 动作!

⚠️ 注意事项

  • 线程数量必须严格匹配 parties 参数;
  • 若某个线程在 await() 前崩溃,会导致其他线程永久阻塞(可用超时 await(timeout) 避免);
  • 支持在屏障动作中执行汇总逻辑(构造函数第二个参数)。

四、对比总结

工具类是否可重用主要用途关键方法
CountDownLatch❌ 否一次性等待多个任务完成countDown(), await()
Semaphore✅ 是控制并发访问数量(限流)acquire(), release()
CyclicBarrier✅ 是多线程阶段性同步await()

✅ 最佳实践建议

  1. 优先使用 try-with-resources 或 finally 确保资源释放
  2. 高并发下慎用阻塞操作,尽量使用带超时的方法;
  3. 不要滥用线程,结合线程池(如 @Async + ThreadPoolTaskExecutor)更安全;
  4. 测试时模拟高并发:可用 jmeterCompletableFuture 并发调用验证。

结语

JUC 工具类是 Java 高并发编程的基石。掌握 CountDownLatchSemaphoreCyclicBarrier,能让你在面对复杂并发场景时游刃有余。记住:工具虽好,用错反噬——务必注意资源释放和异常处理!

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值