此实现是根据GUAVA实现的令牌桶算法限流的思路,在每次尝试获取token时进行计算当前可获得的token数量,相当于懒汉式生成令牌,无需额外的线程去生成令牌。况且,额外线程去生成令牌,需要消耗线程资源,在并发量较高时,定时任务并不准确,不能准确生成令牌。
package concurrencylimit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
/**
* 令牌桶算法
*
* @author nekle
* @since 2022/8/9
*/
public class TokenBucket {
// 上一次获取令牌的时间
private AtomicLong beforeAtomic;
// 生成令牌的时间间隔
private int interval;
// 最大令牌数量
private int max;
// 用于控制令牌数量修改的可重入锁
private ReentrantLock lock;
public TokenBucket(int interval, int max) {
this.beforeAtomic = new AtomicLong(System.currentTimeMillis() - interval * max);
this.interval = interval;
this.max = max;
this.lock = new ReentrantLock(true);
}
public boolean acquire() {
boolean flag = false;
// 当前线程自旋尝试获取令牌
while (!flag) {
// 当前获取令牌时间
long now = System.currentTimeMillis();
// 上一次获取令牌时间
long before = this.beforeAtomic.get();
// 计算时间间隔内生成的令牌数
long tokenSum = ((now - before) / interval);
// 有剩余令牌,则尝试获取令牌
if (tokenSum > 0) {
boolean locked = false;
// 如果令牌数量超过设置的上限,则将令牌数量归于上限值
if (tokenSum > max) {
// 可重入锁对令牌数修改操作进并发访问控制
locked = lock.tryLock();
if (locked) {
// 成功拿到锁,则进行修改,将令牌数量归于上限值
this.beforeAtomic.set(now - interval * max + interval);
flag = true;
lock.unlock();
}
}
// 若尝试获得锁失败,说明另一个线程正在修改最后一次获得令牌的时间
if (!locked) {
// CAS尝试修改最后一次获取令牌的时间,成功则完成这次获取令牌操作,否则继续自旋尝试获得令牌
flag = this.beforeAtomic.compareAndSet(before, before + interval);
}
} else {
// 无剩余令牌,尝试获取令牌失败
return false;
}
}
return true;
}
}
最近在学习令牌桶算法,自己实现了一个,欢迎各位交流学习!!