前置思想
首先明确一点,削峰限流不一定非要用MQ队列,其实JDK就给我们提供了良好的环境Semaphore他主要作用是以【时间换空间】也就是我们现在处理不了的请求让他稍等一会,之后我们闲暇的空间了在处理他,此处主要是JDK已经实现了削峰限流所以手撸一个让自己更加深刻。
主要思想
Semaphore无非就是将不能处理的请求放入队列中,在可以处理的时候拿出。
主要使用到的类
- LinkedBlockingQueue:此处当然少不了队列,当然此队列还有一个作用,谁哪的令牌谁放回去
- AtomicInteger:主要判断什么时候进行阻塞
- LockSupport:线程状态的切换
交代完了开怼!!
/**
* 用于存储线程
*/
private LinkedBlockingQueue<Thread> requestQueue = new LinkedBlockingQueue<>();
/**
* 外部传入令牌数量,用于什么时候让线程进入阻塞
*/
private AtomicInteger tokenNumber;
public MySemaphore(Integer tokenNumber) {
this.tokenNumber = new AtomicInteger(tokenNumber);
}
这步没啥可说的继续!!
/**
* 是否能获取令牌
*/
public boolean tryAcquire() {
if (!requestQueue.contains(Thread.currentThread())) {
requestQueue.add(Thread.currentThread());
}
return tokenNumber.get() > 0;
}
主要判断是否可以获取令牌,以及不将重复的线程放入队列中
/**
* 获取令牌
*/
public void acquire() {
//当前令牌捅中还有令牌,则进行自减,没有则阻塞进入等待队列
if ( tryAcquire()) {
tokenNumber.decrementAndGet();
} else {
LockSupport.park();
}
}
获取令牌则将筒中的令牌-1,没有则阻塞
/**
* 用完了释放令牌 只有是当前队列中的线程才允许释放令牌,并且检测是否在休眠线程,是则唤醒
*/
public void release() {
if (requestQueue.remove(Thread.currentThread())) {
tokenNumber.incrementAndGet();
for (Thread thread : requestQueue) {
if (!thread.getState().equals(Thread.State.RUNNABLE)) {
LockSupport.unpark(requestQueue.peek());
}
}
}
}
当然是有借有还再借不难啦,此处主要考虑的是是否是当前队列中的线程还回令牌,以及线程的状态,主要是以唤醒了别再二次唤醒了,当然这地方只考虑到唤醒了就不唤醒了,还没考虑线程其他状态
PS:此处还差公平,但是一直实现不了,主要还剩一个死活不被唤醒很蛋疼!如有大手看到请不吝啬赐教!!!