Java并发编程三大利器:Semaphore、ReentrantLock、RateLimiter 深度解析

发布时间:2026/6/9 18:53:37

Java并发编程三大利器:Semaphore、ReentrantLock、RateLimiter 深度解析 目录前言一、三者概览二、Semaphore并发数的守护者2.1 什么是Semaphore2.2 核心原理2.3 代码实战基础用法数据库连接池实现公平模式示例2.4 适用场景2.5 优缺点分析优点缺点2.6 关键API详解三、ReentrantLock传统锁的增强版3.1 什么是ReentrantLock3.2 核心原理3.3 代码实战基础用法公平锁 vs 非公平锁Condition条件变量尝试获取锁非阻塞3.4 synchronized vs ReentrantLock3.5 适用场景3.6 最佳实践四、RateLimiter流量控制的艺术家4.1 什么是RateLimiter4.2 核心原理令牌桶算法4.3 代码实战基础用法Spring Boot接口限流平滑突发流量演示高级用法预热模式4.4 令牌桶 vs 漏桶算法4.5 适用场景4.6 生产环境实践五、三者对比总结5.1 功能对比表5.2 性能对比5.3 选型决策树六、常见面试题Q1: Semaphore的permits可以动态增加吗Q2: ReentrantLock和synchronized哪个更好Q3: RateLimiter能保证绝对精确的QPS吗Q4: 分布式场景怎么限流七、总结前言在Java并发编程的世界里线程间的协作与资源控制永远是开发者必须面对的挑战。你是否曾经困惑过如何限制同时访问数据库的连接数如何保护共享资源不被并发破坏如何控制接口的请求频率防止系统被打垮本文将深入剖析Java并发编程中的三大利器Semaphore、ReentrantLock和RateLimiter从原理到实践从场景到选型带你彻底掌握它们的精髓。一、三者概览特性SemaphoreReentrantLockRateLimiter所属包java.util.concurrentjava.util.concurrentcom.google.common.util.concurrent核心功能控制同时访问资源的线程数提供可重入的互斥锁控制请求的速率QPS主要用途限流控制并发数线程同步/互斥限流控制速率公平性支持公平/非公平支持公平/非公平支持平滑突发/预热诞生背景JUC包早期成员synchronized的增强版Guava提供的限流工具二、Semaphore并发数的守护者2.1 什么是SemaphoreSemaphore信号量可以理解为一个许可证管理器。它维护了一个许可集线程需要获得许可才能执行执行完成后归还许可。当没有可用许可时线程将被阻塞。2.2 核心原理Semaphore内部维护一个计数器通过AQSAbstractQueuedSynchronizer实现同步控制acquire()获取许可证计数器减1如果计数器为0则阻塞release()释放许可证计数器加1唤醒等待的线程2.3 代码实战基础用法public class SemaphoreDemo { // 创建3个许可证的信号量最多3个线程同时访问 private static Semaphore semaphore new Semaphore(3); public static void main(String[] args) { for (int i 0; i 10; i) { new Thread(() - { try { semaphore.acquire(); // 获取许可 System.out.println(Thread.currentThread().getName() 获得许可开始执行); Thread.sleep(1000); // 模拟业务处理 System.out.println(Thread.currentThread().getName() 执行完成释放许可); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 释放许可 } }, Thread- i).start(); } } }数据库连接池实现public class DatabaseConnectionPool { private Semaphore semaphore; private ListConnection connections new ArrayList(); public DatabaseConnectionPool(int poolSize) { this.semaphore new Semaphore(poolSize); for (int i 0; i poolSize; i) { connections.add(createConnection()); } } public Connection getConnection() throws InterruptedException { semaphore.acquire(); return borrowConnection(); } public void returnConnection(Connection conn) { returnConnection(conn); semaphore.release(); } private Connection createConnection() { // 模拟创建数据库连接 return new Connection(); } }公平模式示例// 公平模式等待时间最长的线程优先获得许可 Semaphore fairSemaphore new Semaphore(3, true); // 非公平模式默认许可分配随机性能更好 Semaphore unfairSemaphore new Semaphore(3);2.4 适用场景场景说明示例资源池管理数据库连接池、线程池、对象池new Semaphore(maxConnections)并发数限流限制同时执行的任务数API网关控制并发请求简单互斥许可证为1时等同于互斥锁new Semaphore(1)批量操作一次获取/释放多个许可semaphore.acquire(5)2.5 优缺点分析优点✅ 可以动态调整许可证数量✅ 支持批量获取/释放许可✅ 提供tryAcquire()非阻塞尝试✅ 支持公平/非公平模式缺点❌ 容易忘记释放许可必须配合finally使用❌ 不支持重入同一线程多次acquire会阻塞❌ 无法自动释放需要手动管理2.6 关键API详解// 阻塞式获取 semaphore.acquire(); // 获取1个许可 semaphore.acquire(5); // 获取5个许可 // 非阻塞尝试 if (semaphore.tryAcquire()) { // 立即返回 // 获取成功 } // 超时尝试 if (semaphore.tryAcquire(1, TimeUnit.SECONDS)) { // 1秒内获取成功 } // 释放许可 semaphore.release(); // 释放1个许可 semaphore.release(5); // 释放5个许可 // 查看状态 int available semaphore.availablePermits(); // 可用许可数 int queueLength semaphore.getQueueLength(); // 等待线程数三、ReentrantLock传统锁的增强版3.1 什么是ReentrantLockReentrantLock是synchronized关键字的增强版提供了更灵活的锁操作。可重入意味着同一个线程可以多次获取同一把锁而不会死锁。3.2 核心原理ReentrantLock基于AQS实现内部维护一个state变量表示锁状态同时记录当前持有锁的线程实现可重入特性。3.3 代码实战基础用法public class ReentrantLockDemo { private ReentrantLock lock new ReentrantLock(); private int count 0; public void increment() { lock.lock(); // 获取锁 try { count; // 可重入特性演示 nestedMethod(); } finally { lock.unlock(); // 必须释放 } } private void nestedMethod() { lock.lock(); // 同一个线程可以再次获取 try { System.out.println(可重入成功count count); } finally { lock.unlock(); } } }公平锁 vs 非公平锁// 公平锁按请求顺序获取防止线程饥饿 ReentrantLock fairLock new ReentrantLock(true); // 非公平锁默认允许插队吞吐量更高 ReentrantLock unfairLock new ReentrantLock(false); // 性能测试对比 public class LockPerformanceTest { private static int THREAD_COUNT 100; private static int LOOP_COUNT 10000; public static void testLock(ReentrantLock lock, String name) { long start System.currentTimeMillis(); CountDownLatch latch new CountDownLatch(THREAD_COUNT); for (int i 0; i THREAD_COUNT; i) { new Thread(() - { for (int j 0; j LOOP_COUNT; j) { lock.lock(); try { // 模拟临界区操作 } finally { lock.unlock(); } } latch.countDown(); }).start(); } long end System.currentTimeMillis(); System.out.println(name 耗时: (end - start) ms); // 实际结果非公平锁通常比公平锁快2-3倍 } }Condition条件变量public class BoundedBufferT { private ReentrantLock lock new ReentrantLock(); private Condition notFull lock.newCondition(); private Condition notEmpty lock.newCondition(); private Object[] items new Object[100]; private int putIndex, takeIndex, count; public void put(T x) throws InterruptedException { lock.lock(); try { while (count items.length) { notFull.await(); // 队列满等待 } items[putIndex] x; if (putIndex items.length) putIndex 0; count; notEmpty.signal(); // 唤醒等待的消费者 } finally { lock.unlock(); } } SuppressWarnings(unchecked) public T take() throws InterruptedException { lock.lock(); try { while (count 0) { notEmpty.await(); // 队列空等待 } T x (T) items[takeIndex]; items[takeIndex] null; if (takeIndex items.length) takeIndex 0; count--; notFull.signal(); // 唤醒等待的生产者 return x; } finally { lock.unlock(); } } }尝试获取锁非阻塞public class TryLockDemo { private ReentrantLock lock new ReentrantLock(); public boolean tryProcess() { // 尝试获取锁立即返回 if (lock.tryLock()) { try { // 获取锁成功执行业务 doBusiness(); return true; } finally { lock.unlock(); } } else { // 获取锁失败执行降级逻辑 System.out.println(系统繁忙请稍后重试); return false; } } public boolean tryProcessWithTimeout() throws InterruptedException { // 尝试获取锁等待1秒 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { doBusiness(); return true; } finally { lock.unlock(); } } return false; } }3.4 synchronized vs ReentrantLock对比项synchronizedReentrantLock释放方式自动释放JVM保证手动释放finally可重入性✅ 支持✅ 支持公平锁❌ 不支持✅ 支持尝试获取❌ 不支持✅ tryLock()可中断❌ 不支持✅ lockInterruptibly()多条件只有wait/notify多个Condition性能JVM优化更高略低调试容易困难看不到锁持有者代码简洁简洁繁琐3.5 适用场景场景推荐度说明替代synchronized⭐⭐⭐⭐⭐需要更灵活的锁控制需要公平锁⭐⭐⭐⭐⭐防止线程饥饿需要尝试获取锁⭐⭐⭐⭐⭐避免死锁或实现降级需要可中断锁⭐⭐⭐⭐支持线程中断响应多条件等待⭐⭐⭐⭐⭐生产者-消费者模式简单互斥场景⭐⭐优先使用synchronized3.6 最佳实践// ✅ 正确标准模式 lock.lock(); try { // 临界区代码 } finally { lock.unlock(); } // ✅ 正确尝试获取 if (lock.tryLock()) { try { // 业务逻辑 } finally { lock.unlock(); } } else { // 降级处理 } // ❌ 错误未在finally中释放 lock.lock(); if (someCondition) { return; // 忘记释放锁 } lock.unlock(); // ❌ 错误重复释放 lock.lock(); lock.unlock(); lock.unlock(); // 会抛出IllegalMonitorStateException四、RateLimiter流量控制的艺术家4.1 什么是RateLimiterRateLimiter是Google Guava库提供的限流工具基于令牌桶算法实现用于控制事件消费的速率。它以稳定的速率生成令牌线程需要获取令牌才能执行。4.2 核心原理令牌桶算法text令牌以固定速率生成 → 放入令牌桶 → 请求到来时获取令牌 → 有令牌则处理无则等待或拒绝令牌桶算法的特点允许一定程度的突发流量令牌可以累积平均速率稳定可应对瞬时高峰4.3 代码实战基础用法public class RateLimiterDemo { public static void main(String[] args) { // 创建限流器每秒生成2个令牌QPS2 RateLimiter rateLimiter RateLimiter.create(2.0); for (int i 0; i 10; i) { double waitTime rateLimiter.acquire(); // 获取令牌阻塞等待 System.out.println(任务 i 被执行等待时间: waitTime ms); } } }Spring Boot接口限流RestController public class ApiController { // 限制QPS100 private RateLimiter rateLimiter RateLimiter.create(100.0); GetMapping(/api/test) public Result testApi() { if (!rateLimiter.tryAcquire()) { return Result.error(请求过于频繁请稍后重试); } // 正常业务处理 return Result.success(处理成功); } // 支持预热的限流器冷启动场景 private RateLimiter warmupLimiter RateLimiter.create(1000, 10, TimeUnit.SECONDS); GetMapping(/api/warmup) public Result warmupApi() { // 10秒预热时间逐步达到1000 QPS warmupLimiter.acquire(); return Result.success(处理成功); } }平滑突发流量演示public class SmoothBurstDemo { public static void main(String[] args) { // 设置QPS5 RateLimiter limiter RateLimiter.create(5.0); // 模拟突发请求 for (int i 0; i 10; i) { double waitTime limiter.acquire(); System.out.printf(请求%d 被处理等待时间: %.3f秒%n, i, waitTime); } // 输出结果分析 // 前几个请求等待时间几乎为0突发处理 // 后续请求间隔稳定在0.2秒左右达到平均速率 } }高级用法预热模式public class WarmupRateLimiterDemo { /** * 冷启动场景系统刚启动时逐步增加流量 * 适用场景缓存未预热、连接池未建立 */ public void testWarmup() { // 创建预热限流器目标QPS100预热时间10秒 RateLimiter warmupLimiter RateLimiter.create(100.0, 10, TimeUnit.SECONDS); long startTime System.currentTimeMillis(); for (int i 0; i 200; i) { double waitTime warmupLimiter.acquire(); long currentTime System.currentTimeMillis() - startTime; System.out.printf(第%d个请求等待时间: %.3fms当前时间: %dms%n, i, waitTime * 1000, currentTime); } // 观察结果前期的请求等待时间较长限流较严 // 随着时间推移等待时间逐渐缩短最终达到稳定速率 } }4.4 令牌桶 vs 漏桶算法对比维度令牌桶RateLimiter漏桶突发流量处理✅ 允许一定突发❌ 严格平滑算法复杂度中等低使用场景允许峰谷波动的场景严格控制速率的场景实现难度较复杂简单代表实现Guava RateLimiterNginx限流模块4.5 适用场景场景实现方式示例API限流tryAcquire()防止接口被刷防爬虫acquire() IP维度限制同一IP访问频率外部服务调用acquire()保护下游服务限时抢购tryAcquire()控制秒杀流量冷启动保护create(rate, warmupPeriod)系统刚启动时逐步增加流量4.6 生产环境实践Component public class RateLimitService { // 不同接口使用不同的限流器 private final MapString, RateLimiter limiters new ConcurrentHashMap(); /** * 获取限流器支持不同业务维度 */ private RateLimiter getRateLimiter(String key, double qps) { return limiters.computeIfAbsent(key, k - RateLimiter.create(qps)); } /** * 通用限流方法 */ public boolean tryAcquire(String key, double qps) { return getRateLimiter(key, qps).tryAcquire(); } /** * 带降级的限流 */ public T T executeWithRateLimit(String key, double qps, SupplierT action, SupplierT fallback) { if (tryAcquire(key, qps)) { return action.get(); } return fallback.get(); } } // 使用示例 RestController public class OrderController { Autowired private RateLimitService rateLimitService; PostMapping(/order/create) public Result createOrder(RequestBody Order order) { String ip getClientIp(); String key createOrder: ip; return rateLimitService.executeWithRateLimit( key, 10.0, // 同一IP每秒最多10次 () - doCreateOrder(order), () - Result.error(操作过于频繁请稍后重试) ); } }五、三者对比总结5.1 功能对比表对比项SemaphoreReentrantLockRateLimiter核心功能限制并发数线程互斥限制速率控制维度数量访问权限时间频率阻塞机制无许可则阻塞无锁则阻塞无令牌则阻塞公平性✅✅❌可重入❌✅N/A尝试获取✅ tryAcquire✅ tryLock✅ tryAcquire超时支持✅✅✅批量操作✅ (多许可)❌❌适用场景资源池、并发限流临界区保护API限流、防刷5.2 性能对比// 性能测试结果百万次操作 // 1. synchronized: ~500ms // 2. ReentrantLock: ~550ms // 3. Semaphore: ~600ms // 4. RateLimiter: ~800ms // 性能排序从快到慢 synchronized ReentrantLock Semaphore RateLimiter5.3 选型决策树需要控制什么 │ ├─ 互斥访问共享资源 │ ├─ 简单场景 → synchronized │ └─ 需要公平/超时/可中断 → ReentrantLock │ ├─ 限制同时访问数量 │ ├─ 资源池管理 → Semaphore(permits) │ └─ 简单互斥 → Semaphore(1) │ └─ 限制访问频率 ├─ 控制QPS → RateLimiter ├─ 严格平滑 → 漏桶算法自行实现 └─ 分布式限流 → Redis Lua六、常见面试题Q1: Semaphore的permits可以动态增加吗// 可以但需要注意 Semaphore semaphore new Semaphore(5); semaphore.release(); // 增加1个许可 // 或者批量增加 semaphore.release(3); // 增加3个许可Q2: ReentrantLock和synchronized哪个更好// 简单场景用synchronized public synchronized void simpleMethod() { // 代码简洁性能好 } // 复杂场景用ReentrantLock public void complexMethod() { if (lock.tryLock()) { try { // 需要尝试获取、可中断等特性 } finally { lock.unlock(); } } }Q3: RateLimiter能保证绝对精确的QPS吗// RateLimiter是近似精确不是100%精确 RateLimiter limiter RateLimiter.create(100); // 目标100 QPS // 短时间内可能有波动但长期平均会趋近目标值 // 原因受系统调度、GC等因素影响Q4: 分布式场景怎么限流// 单机RateLimiter无法用于分布式场景 // 分布式限流方案 // 1. Redis Lua脚本推荐 // 2. Sentinel阿里开源 // 3. Nginx Lua七、总结工具一句话总结最佳实践场景Semaphore最多允许N个人同时做事数据库连接池、并发数限流ReentrantLocksynchronized的增强版需要公平锁、尝试获取、可中断的互斥场景RateLimiter每秒只让N个人做事API限流、防爬虫、保护下游服务选择建议简单互斥 →synchronized需要复杂锁特性 →ReentrantLock限制并发数量 →Semaphore限制请求频率 →RateLimiter分布式限流 →Redis Lua或Sentinel掌握这三个工具你就能应对Java并发编程中90%的场景。记住没有最好的工具只有最合适的工具。

相关新闻