首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 16 | Semaphore:如何快速实现一个限流器? 在Java并发编程的广阔领域中,限流器(Rate Limiter)是一种重要的控制机制,用于限制对共享资源的访问速率,以防止系统过载或资源耗尽。Semaphore(信号量)作为Java并发包`java.util.concurrent`中的一个重要工具,提供了一种灵活的方式来控制同时访问某个特定资源或资源池的操作数量,从而成为实现限流器的一种有效手段。本章节将深入探讨如何使用Semaphore来快速实现一个高效、灵活的限流器。 #### 16.1 Semaphore基础概念 Semaphore,即信号量,是一种提供不同线程间或进程间同步手段的原语。它维护了一个许可集,线程可以通过获取(acquire)许可来访问某个资源,并在访问完毕后释放(release)许可。Semaphore支持两种主要的操作: - **acquire()**:如果许可数大于0,则线程从信号量获取一个许可并继续执行;如果许可数为0,则线程阻塞,直到有许可可用。 - **release()**:线程释放它持有的许可,从而增加信号量的许可数,并可能允许其他因等待许可而被阻塞的线程继续执行。 信号量还可以配置为非公平(默认)或公平模式,非公平模式下,等待线程的获取顺序是不确定的,而公平模式下,线程将按照它们请求许可的顺序来获取许可。 #### 16.2 Semaphore作为限流器的优势 将Semaphore用作限流器,主要基于其以下几个优势: 1. **简单直观**:Semaphore的API设计简洁,易于理解和使用,开发者可以快速上手。 2. **灵活性强**:通过调整信号量的初始许可数,可以精确控制资源的并发访问数量,满足不同的限流需求。 3. **可重用性高**:同一个Semaphore实例可以被多个线程重复使用,实现高效的资源访问控制。 4. **集成性好**:Java并发包提供了丰富的同步和并发工具,Semaphore可以与其他并发工具(如CountDownLatch、CyclicBarrier等)无缝集成,构建复杂的并发系统。 #### 16.3 实现一个基于Semaphore的限流器 接下来,我们将通过一个具体的示例来展示如何使用Semaphore实现一个简单的限流器。假设我们有一个API接口,该接口每秒只能被调用100次,以保护后端服务不被过度请求压垮。 **步骤1:定义Semaphore实例** 首先,我们需要根据限流要求定义一个Semaphore实例。在这个例子中,我们希望每秒处理100个请求,因此可以设置一个合适的许可数。但需要注意的是,Semaphore本身并不直接支持基于时间的许可管理,因此我们需要结合其他机制(如定时任务)来实现。不过,为了简化示例,这里我们直接根据并发请求量来设置许可数,不严格限制时间间隔。 ```java import java.util.concurrent.Semaphore; public class RateLimiter { private final Semaphore semaphore; public RateLimiter(int permitsPerSecond) { // 假设我们希望每秒处理permitsPerSecond个请求 // 这里直接以permitsPerSecond作为初始许可数,实际使用中需要更复杂的逻辑来动态管理 this.semaphore = new Semaphore(permitsPerSecond); } public void tryAcquire() throws InterruptedException { semaphore.acquire(); // 尝试获取许可 } public void release() { semaphore.release(); // 释放许可 } } ``` **注意**:上述代码示例中的Semaphore并未直接实现基于时间的限流。在真实场景中,你可能需要结合使用`ScheduledExecutorService`来定时增加Semaphore的许可数,或者使用如Guava库中的`RateLimiter`类(它内部实现了基于时间的平滑限流算法)来更精确地控制。 **步骤2:在业务逻辑中应用限流器** 在API接口的实现中,我们可以使用上面定义的`RateLimiter`类来控制请求的接入速率。 ```java public class ApiService { private final RateLimiter rateLimiter; public ApiService(RateLimiter rateLimiter) { this.rateLimiter = rateLimiter; } public void handleRequest() { try { rateLimiter.tryAcquire(); // 尝试获取许可 // 处理请求逻辑... System.out.println("处理请求"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 保留中断状态 System.out.println("请求被中断"); } finally { rateLimiter.release(); // 无论请求是否成功处理,都释放许可 } } } ``` **步骤3:测试限流器** 为了验证限流器的效果,你可以编写一个简单的测试程序,模拟多个并发请求访问`ApiService`。 ```java public class RateLimiterTest { public static void main(String[] args) { RateLimiter rateLimiter = new RateLimiter(10); // 假设每秒允许10个请求 ApiService apiService = new ApiService(rateLimiter); // 模拟多个并发请求 for (int i = 0; i < 20; i++) { new Thread(() -> { apiService.handleRequest(); }).start(); } } } ``` 在上面的测试中,由于我们设置了每秒只允许10个请求,而实际上我们模拟了20个并发请求,因此可以观察到部分请求会因为无法立即获取到许可而被阻塞,直到有足够的许可可用。 #### 16.4 进一步优化与考虑 虽然上述示例展示了如何使用Semaphore实现基本的限流功能,但在实际生产环境中,你可能还需要考虑以下几个方面来进一步优化限流器: - **动态调整许可数**:根据系统负载实时调整Semaphore的许可数,以应对不同的流量压力。 - **基于时间的精确限流**:结合定时任务或时间戳计算,实现基于时间的精确限流。 - **异常处理与日志记录**:在限流逻辑中加入适当的异常处理和日志记录,以便监控和调试。 - **集成监控与报警**:将限流器的状态集成到监控系统中,并在达到特定阈值时触发报警,以便及时响应。 通过以上步骤,你可以使用Java中的Semaphore来快速实现一个高效、灵活的限流器,从而保护你的系统免受过度请求的冲击。
上一篇:
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
下一篇:
17 | ReadWriteLock:如何快速实现一个完备的缓存?
该分类下的相关小册推荐:
Mybatis合辑3-Mybatis动态SQL
Java语言基础8-Java多线程
Mybatis合辑5-注解、扩展、SQL构建
Java语言基础6-面向对象高级
Java面试指南
SpringBoot合辑-初级篇
Java性能调优实战
Java语言基础4-数组详解
Java必知必会-Maven初级
Java语言基础12-网络编程
Java语言基础16-JDK8 新特性
Mybatis合辑4-Mybatis缓存机制