Java并发编程三大利器:Semaphore、ReentrantLock、RateLimiter 深度解析
2026/6/9 18:53:36 网站建设 项目流程

目录

前言

一、三者概览

二、Semaphore:并发数的守护者

2.1 什么是Semaphore?

2.2 核心原理

2.3 代码实战

基础用法

数据库连接池实现

公平模式示例

2.4 适用场景

2.5 优缺点分析

优点

缺点

2.6 关键API详解

三、ReentrantLock:传统锁的增强版

3.1 什么是ReentrantLock?

3.2 核心原理

3.3 代码实战

基础用法

公平锁 vs 非公平锁

Condition条件变量

尝试获取锁(非阻塞)

3.4 synchronized vs ReentrantLock

3.5 适用场景

3.6 最佳实践

四、RateLimiter:流量控制的艺术家

4.1 什么是RateLimiter?

4.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并发编程中的三大利器:SemaphoreReentrantLockRateLimiter,从原理到实践,从场景到选型,带你彻底掌握它们的精髓。

一、三者概览

特性SemaphoreReentrantLockRateLimiter
所属包java.util.concurrentjava.util.concurrentcom.google.common.util.concurrent
核心功能控制同时访问资源的线程数提供可重入的互斥锁控制请求的速率(QPS)
主要用途限流(控制并发数)线程同步/互斥限流(控制速率)
公平性支持公平/非公平支持公平/非公平支持平滑突发/预热
诞生背景JUC包早期成员synchronized的增强版Guava提供的限流工具

二、Semaphore:并发数的守护者

2.1 什么是Semaphore?

Semaphore(信号量)可以理解为一个许可证管理器。它维护了一个许可集,线程需要获得许可才能执行,执行完成后归还许可。当没有可用许可时,线程将被阻塞。

2.2 核心原理

Semaphore内部维护一个计数器,通过AQS(AbstractQueuedSynchronizer)实现同步控制:

  • 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 List<Connection> 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 什么是ReentrantLock?

ReentrantLock是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 BoundedBuffer<T> { 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⭐⭐⭐⭐⭐需要更灵活的锁控制
需要公平锁⭐⭐⭐⭐⭐防止线程饥饿
需要尝试获取锁⭐⭐⭐⭐⭐避免死锁或实现降级
需要可中断锁⭐⭐⭐⭐支持线程中断响应
多条件等待⭐⭐⭐⭐⭐生产者-消费者模式
简单互斥场景⭐⭐优先使用synchronized

3.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 什么是RateLimiter?

RateLimiter是Google Guava库提供的限流工具,基于令牌桶算法实现,用于控制事件消费的速率。它以稳定的速率生成令牌,线程需要获取令牌才能执行。

4.2 核心原理:令牌桶算法

text

令牌以固定速率生成 → 放入令牌桶 → 请求到来时获取令牌 → 有令牌则处理,无则等待或拒绝

令牌桶算法的特点:

  • 允许一定程度的突发流量(令牌可以累积)

  • 平均速率稳定

  • 可应对瞬时高峰

4.3 代码实战

基础用法
public class RateLimiterDemo { public static void main(String[] args) { // 创建限流器:每秒生成2个令牌(QPS=2) 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 { // 限制QPS=100 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) { // 设置QPS=5 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() { // 创建预热限流器:目标QPS=100,预热时间=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 Map<String, 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, Supplier<T> action, Supplier<T> 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 > RateLimiter

5.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个人同时做事"数据库连接池、并发数限流
ReentrantLock"synchronized的增强版"需要公平锁、尝试获取、可中断的互斥场景
RateLimiter"每秒只让N个人做事"API限流、防爬虫、保护下游服务

选择建议:

  • 简单互斥 →synchronized

  • 需要复杂锁特性 →ReentrantLock

  • 限制并发数量 →Semaphore

  • 限制请求频率 →RateLimiter

  • 分布式限流 →Redis + LuaSentinel

掌握这三个工具,你就能应对Java并发编程中90%的场景。记住:没有最好的工具,只有最合适的工具

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询