背景
在实际开发中会碰到一个接口特别耗时,需要进行大量的数据处理和计算。如果不限制接口的请求调用,将会有大量的请求阻塞,导致服务器崩溃。如何解决这种问题呢?下面我们一起看下如何通过Semaphore来实现接口的访问限制。
简介
Semaphore(信号量)是java.util.current包中的一个对象。简单来讲就是通过锁的方式去获取信号量,如果获取到则执行,否则不执行
主要应用在控制并发量,一段时间内只允许特定的线程去执行,甚至可以控制并发量为1,也就是同步执行某一个方法
主要方法
-
Semaphore semaphore = new Semaphore(1); 通过这个方式创建信号量对象,初始化参数是许可总数
-
availablePermits 获取当前存在的许可
-
acquire 争取信号,该方式是先通过自旋锁的方式获取信息,直到permits小于0,然后再通过阻塞的方式去等待的获取许可
-
tryAcquire 非阻塞的方式获取信号量,通过自旋锁的方式获取,如果获取失败直接返回
-
tryAcquire(long timeout, TimeUnit unit) 再特定的实际内阻塞,获取信号量,获取失败返回false
-
release 释放许可,需要注意的是该方法可以传一个参数,释放许可的量,这个方法会扩充许可量,最后在try finally中执行,然后判断是否成功获取许可后再释放许可
代码分析和解读
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);
for (int i = 0; i < 100; i++) {
MyThread thread = new MyThread(semaphore);
thread.setName(i + 1+"");
thread.start();
}
}
static class MyThread extends Thread {
Semaphore semaphore;
public MyThread(Semaphore semaphore){
this.semaphore = semaphore;
}
@Override
public void run() {
boolean acquire = false;
try {
int i = semaphore.availablePermits();
String name = Thread.currentThread().getName();
if (i < 1) {
System.out.println(String.format("线程%s没获取到:%s", name, i));
return;
}
System.out.println(String.format("线程%s开始争取信号!!:%s", name, i));
Thread.sleep(1000);
// if(Integer.valueOf(name)>50){
// throw new RuntimeException("当前信号量:"+semaphore.availablePermits());
// }
acquire = semaphore.tryAcquire();
if (acquire) {
System.out.println(Thread.currentThread().getName()+"争取到信号:开始执行"+semaphore.availablePermits());
Thread.sleep(5000);
}
System.out.println(Thread.currentThread().getName() + " " + semaphore.getQueueLength());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(acquire){
semaphore.release(1);
}
}
}
}
}
tryAcquire 适合用在一个程序的执行时间特别长
假设在一个非常大的并发的情况下,有大量线程读取availablePermits是大于0的,这个适合都会通过tryAcquire 或acquire方法去获取许可;如果使用acquire去获取,假设这个后续的执行线程需要占用这个许可大量的时间,这就导致其他线程在阻塞状态,这会导致资源得不到释放,高并发情况下会导致内存飙升,甚至僚机
acquire适合用在执行时间特别短,或需要某一个方法有比较高的成功率
acquire方法在获取许可的时候,先是通过自旋的方式获取,然后再是阻塞等待,直到前面的线程执行完成释放许可,在执行时间特别短的程序可以使用这种方式,会有一个比较高的几率获取到许可,然后成功执行程序。如果用tryAcquire的方式,则会出现大量无效的请求
release方法注意 事项
需要确定释放获取到许可了再在finally中释放许可,否则碰到异常的情况,acquire方法没获取到许可,在finally中释放了许可,会导致许可量大大增
生产实践
该模块已经运用到华宇的在线Excel平台中,用来限制数据清洗等耗时的接口请求。
代码展示
这里是通过AOP和Semaphore来限制接口的请求
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLimitAp {
String value() default "";
SysSemaphoreLimitAspect.ModuleSemaphoreEnum semaphore();
}
package com.huayu.sys.core.aop;
import com.huayu.sys.common.BaseResult;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.Semaphore;
@Aspect
@Component
@Slf4j
public class SysSemaphoreLimitAspect {
@Pointcut("@annotation(com.huayu.sys.core.aop.SysLimitAp)")
public void logPointCut() {
}
public final static String SERVICE_BUSY_RESPONSE = "正在处理服务请求,请稍后再试";
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
SysLimitAp annotation = method.getAnnotation(SysLimitAp.class);
if (annotation == null) {
throw new IllegalArgumentException("annotation不能为空");
}
ModuleSemaphoreEnum semaphoreEnum = annotation.semaphore();
if (semaphoreEnum == null) {
throw new IllegalArgumentException("semaphoreEnum参数不能为空");
}
Semaphore semaphore = semaphoreEnum.semaphore;
int availablePermits=semaphore.availablePermits();
if(availablePermits<1){
return BaseResult.initBaseResult(SERVICE_BUSY_RESPONSE);
}
boolean hasAcquired = false;
try {
hasAcquired = semaphore.tryAcquire(1);
if(hasAcquired){
Object result = point.proceed();
return result;
}else{
return BaseResult.initBaseResult(SERVICE_BUSY_RESPONSE);
}
} finally {
if (hasAcquired) {
semaphore.release(1);
}
}
}
/**
* 实例枚举
*/
public enum ModuleSemaphoreEnum {
SEMAPHORE_APPLY_SNAPSHOT(1),
SEMAPHORE_PUSH_REPORT(1),
SEMAPHORE_PUSH_EXPAND_REPORT(1);
public int permits;
public Semaphore semaphore;
ModuleSemaphoreEnum(int permits){
this.permits = permits;
this.semaphore = new Semaphore(permits);
}
}
}
具体使用方式:
@SysLimitAp(semaphore = SysSemaphoreLimitAspect.ModuleSemaphoreEnum.SEMAPHORE_PUSH_REPORT)
在接口上使用该注解,我这里使用的是信号量是1,同一时间只允许1个线程去调用该接口。
如果第一次请求没结束,再次访问该接口,可以给前端返回【服务器繁忙】等提示信息。
170万+

被折叠的 条评论
为什么被折叠?



