背景
某些场景下,有可能一个方法不能被并发执行,有可能一个方法的特定参数不能被并发执行。比如不能将一个消息发送多次,创建缓存最好只创建一次等等。为了实现上面的目标我们就需要采用同步机制来完成,但同步的逻辑如何实现呢,是否会影响到原有逻辑呢?
嵌入式
这里讲的嵌入式是说获取锁以及释放锁的逻辑与业务代码耦合在一起,又分分布式与单机两种不同场景的不同实现。
单机版本
下面方法,每个productId不允许并发访问,所以这里可以直接用synchronized来锁定不同的参数。
@Service
public class ProductAppService {
public void invoke(Integer productId) {
synchronized (productId) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("productId:" + productId+" time:"+new Date());
}
}
}
测试脚本:三个相同的参数0,两个不同的参数1和2,通过一个多线程的例子来模似。如果有并发请求的测试工具可能效果会更好。
private void testLock(){
ExecutorService executorService= Executors.newFixedThreadPool(5);
executorService.submit(new Runnable() {
@Override
public void run() {
productAppService.invoke2(0);
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
productAppService.invoke2(0);
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
productAppService.invoke2(0);
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
productAppService.invoke2(1);
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
productAppService.invoke2(2);
}
});
executorService.shutdown();
}
测试结果如下,0,1,2三个请求未被阻塞,后面的两个0被阻塞。
分布式版本
分布式的除了锁机制不同之外其它的测试方法相同,这里只贴出锁的部分:
public void invoke2(Integer productId) {
RLock lock=this.redissonService.getRedisson().getLock(productId.toString());
try {
boolean locked=lock.tryLock(3000,500, TimeUnit.MILLISECONDS);
if(locked){
Thread.sleep(1000);
System.out.print("productId:" + productId+" time:"+new Date());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
嵌入式的缺点
比较明显的就是锁的逻辑与业务逻辑混合在一起,增加了程序复杂度而且也不利于锁机制的更替。
注解式
能否将锁的逻辑隐藏起来,通过在特定方法上增加注解来实现呢?就像Spring Cache的应用。当然是可以的,这里我们只需要解决如下三个问题:
定义注解
锁一般有如下几个属性:
- key,锁对象的标识,就是上面提到的方法的某些参数。一般由方法所属类的完全限定名,方法名以及指定的参数构成。
- maximumWaiteTime,最大等待时间,避免线程死循环。
- expirationTime,锁的生命周期,可以有效避免因特殊原因未释放锁导致其它线程永远获取不到锁的局面。
- timeUnit,配合上面两个属性使用,时间单位。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestLockable {
String[] key() default "";
long maximumWaiteTime() default 2000;
long expirationTime() default 1000;
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
实现注解
由于我们的目标是注解式锁,这里通过AOP的方式来实现,具体依赖AspectJ,创建一个拦截器:
public abstract class AbstractRequestLockInterceptor {
protected abstract Lock getLock(String key);
protected abstract boolean tryLock(long waitTime, long leaseTime, TimeUnit unit,Lock lock) throws InterruptedException;
/**
* 包的表达式目前还有待优化 TODO
*/
@Pointcut("execution(* com.chanjet.csp..*(..)) && @annotation(com.chanjet.csp.product.core.annotation.RequestLockable)")
public void pointcut(){}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable{
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
String targetName = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
Object[] arguments = point.getArgs();
if (method != null && method.isAnnotationPresent(RequestLockable.class)) {
RequestLockable requestLockable = method.getAnnotation(RequestLockable.class);
String requestLockKey = getLockKey(method,targetName, methodName, requestLockable.key(), arguments);
Lock lock=this.getLock(requestLockKey);
boolean isLock = this.tryLock(requestLockable.maximumWaiteTime(),requestLockable.expirationTime(), requestLockable.timeUnit(),lock);
if(isLock) {
try {
return point.proceed();
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("获取锁资源失败");
}
}
return point.proceed();
}
private String getLockKey(Method method,String targetName, String methodName, String[] keys, Object[] arguments) {
StringBuilder sb = new StringBuilder();
sb.append("lock.").append(targetName).append(".").append(methodName);
if(keys != null) {
String keyStr = Joiner.on(".").skipNulls().join(keys);
if(!StringUtils.isBlank(keyStr)) {
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameters =discoverer.getParameterNames(method);
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keyStr);
EvaluationContext context = new StandardEvaluationContext();
int length = parameters.length;
if (length > 0) {
for (int i = 0; i < length; i++) {
context.setVariable(parameters[i], arguments[i]);
}
}
String keysValue = expression.getValue(context, String.class);
sb.append("#").append(keysValue);
}
}
return sb.toString();
}
}
注意如下几点:
- 为什么会存在抽象方法?那是为下面的将注解机制与具体的锁实现解耦服务的,目的是希望注解式锁能够得到复用也便于扩展。
- 锁的key生成规则是什么?前缀一般是方法所在类的完全限定名,方法名称以及spel表达式来构成,避免重复。
- SPEL表达式如何支持?
LocalVariableTableParameterNameDiscoverer它在Spring MVC解析Controller的参数时有用到,可以从一个Method对象中获取参数名称列表。
SpelExpressionParser是标准的spel解析器,利用上面得来的参数名称列表以及参数值列表来获取真实表达式。
问题
基于aspectj的拦截器,@Pointcut中的参数目前未找到动态配置的方法,如果有解决方案的可以告诉我。
将注解机制与具体的锁实现解耦
注解式锁理论上应该与具体的锁实现细节分离,客户端可以任意指定锁,可以是单机下的ReentrantLock也可以是基于redis的分布式锁,当然也可以是基于zookeeper的锁,基于此目的上面我们创建的AbstractRequestLockInterceptor这个拦截器是个抽象类。看下基于redis的分布式锁的子类实现:
@Aspect
public class RedisRequestLockInterceptor extends AbstractRequestLockInterceptor {
@Autowired
private RedissonService redissonService;
private RedissonClient getRedissonClient(){
return this.redissonService.getRedisson();
}
@Override
protected Lock getLock(String key) {
return this.getRedissonClient().getLock(key);
}
@Override
protected boolean tryLock(long waitTime, long leaseTime, TimeUnit unit,Lock lock) throws InterruptedException {
return ((RLock)lock).tryLock(waitTime,leaseTime,unit);
}
}
注解式锁的应用
只需要在需要同步的方法上增加@RequestLockable,然后根据需要指定或者不指定key,也可以根据实际场景配置锁等待时间以及锁的生命周期。
@RequestLockable(key = {"#productId"})
public void invoke3(Integer productId) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("productId:" + productId+" time:"+new Date());
}
当然为了拦截器生效,我们需要在配置文件中配置上拦截器。
<bean class="com.product.api.interceptor.RedisRequestLockInterceptor"></bean>
<aop:aspectj-autoproxy proxy-target-class="true"/>
注解式锁的优点:
- 锁的逻辑与业务代码完全分离,降低了复杂度。
- 灵活的spel表达式可以灵活的构建锁的key。
- 支持多种锁,可以随意切换而不影响业务代码。
相关推荐
如果使用spring mvc 3.2+和servelt 3+容器(比如tomcat8),那么web.xml和applicationContext.xml都不是必须的,可使用基于注解的配置: 基于配置的集成例子源代码:
Java注解如何基于Redission实现分布式锁 Java注解是一种在Java语言中提供元数据的机制,可以用来标注Java代码中的元素,如类、方法、变量等。Redission是一个基于Redis的分布式锁实现库,本文将介绍如何使用Java...
Mybatis-Plus 中乐观锁 @version 注解的问题与解决方案 Mybatis-Plus 是一个基于 Mybatis 的增强工具,它提供了许多实用的功能来简化数据库交互操作。在实际开发中,我们经常会遇到乐观锁的问题,特别是在使用 @...
以下是一个简单的基于ZooKeeper的分布式锁实现: ```java public class ZookeeperDistributedLock implements DistributedLock { private ZooKeeper zooKeeper; private String lockPath; public boolean lock...
springboot项目用于毕业,企业工作者项目代码参考
- 使用互斥锁处理并发请求,防止雪崩。 8. **最佳实践** - 缓存的大小和存活时间应根据业务需求调整。 - 对于更新频繁的数据,考虑使用异步更新策略。 - 使用合适的序列化机制,保证缓存数据的序列化和反序列化...
本篇文章将深入探讨如何通过注解的方式在Java应用中实现基于Redis的分布式锁。 首先,我们要理解什么是分布式锁。分布式锁是分布式系统中的一个概念,它允许多个节点在同一时刻只有一个能够执行特定的操作,以避免...
《基于注解的分布式Redis锁实现详解》 在现代分布式系统中,数据一致性与并发控制是至关重要的问题,而分布式锁作为一种常用的解决手段,被广泛应用。本篇文章将深入探讨一个名为“mlock-demo”的项目,它实现了...
- `@GeneratedValue`: 提供了多种主键生成策略,如`AUTO`(默认,根据数据库自动选择)、`IDENTITY`(基于数据库的Identity字段)、`SEQUENCE`(使用数据库序列)和`TABLE`(使用特定表来生成主键)。 - `@...
* 双线一对一映射:基于外键使用 @OneToOne 注解,基于主键使用 Hibernate 的扩展注解。 在映射关联关系时,需要注意以下几点: * mappedBy 属性:用在双向关联中,把关系的维护权反转。 * cascade 属性:指定级联...
在基于Spring框架的项目中,我们可以通过注解的方式对方法或Controller进行加锁。这通常需要自定义一个AOP切面,当方法执行前,检查Redis中的特定key是否存在,如果不存在则设置key并执行方法,执行完成后删除key。...
过滤器机制简易RPC框架-客户端限流配置简易RPC框架-上下文简易RPC框架-代理简易RPC框架-熔断降级机制简易RPC框架-SPI熔断降级实现影响上下文机制,后续更新解决基于注解的锁Spring boot实践WEBValidator (未同步...
- Zookeeper:Zookeeper提供了一种基于znode的分布式锁实现。创建临时顺序节点,通过比较节点顺序判断锁的归属,同时监听节点变化来实现锁的释放。 3. **优缺点比较** - 声明式分布式锁简化了代码,提高了可读性...
分布式代理锁,动态的锁后缀采用ThreadLocal或者参数名获取 锁粒度自定义选择,目前实现基于redis,后续扩展zk等 SpringBoot,默认使用redisson链接redis,可以更改注解参数使用Spring redis工具 key分为:无后缀、...
本项目提供了一种简单且实用的解决方案,利用Redis作为分布式锁的基础,通过在Spring Boot方法上添加注解来启用锁功能。 首先,我们需要理解分布式锁的基本原理。分布式锁是一种在分布式系统中协调多个节点对共享...
在Java开发中,Spring和Mybatis是两个非常流行的框架,用于管理和操作数据库。Redis则是一个高性能的键值存储...在实际开发中,还可以根据需求调整缓存策略,如设置过期时间、使用分布式锁等,以满足不同业务的需求。
Redisson是基于Redis的Java客户端,它提供了丰富的数据结构和服务,包括分布式锁、信号量、队列、计数器等,极大地扩展了Redis在分布式系统中的应用能力。本篇文章将详细探讨如何使用Redisson实现Redis分布式事务锁...
本方案基于Redis实现分布式锁,并结合自定义注解简化了使用流程,使得开发者可以更轻松地在代码中集成这一功能。 首先,我们来理解自定义注解的原理。自定义注解在Java中是一种元数据,可以为代码提供额外的信息,...
Redisson的分布式锁是基于Redis实现的,利用Redis的高可用性、低延迟以及丰富的数据结构特性,可以有效地解决分布式环境下的锁问题。Redisson通过命令行接口(Command)将Java方法映射到Redis操作上,这样我们就可以...