此做法的目标是使用注解+切面方式完成zk排他锁的功能。
并且使用key作为认知中的锁对象来编写。同一个key为同一把锁,在注解中参数可控制可重入或不可重入。
yml配置
zookeeper:
#集群使用逗号分隔
connectString: 127.0.0.1:2181
baseSleepTimeMs: 3000
maxRetries: 3
配置类创建curator客户端
import lombok.Data;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "zookeeper")
@Data
public class ZookeeperProperties {
private String connectString;
private int baseSleepTimeMs;
private int maxRetries;
private int sessionTimeoutMs = Integer.getInteger("curator-default-session-timeout", 60000);
private int connectionTimeoutMs = Integer.getInteger("curator-default-connection-timeout", 15000);
@ConditionalOnMissingClass
@Bean
public CuratorFramework getClient(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(baseSleepTimeMs,maxRetries);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, sessionTimeoutMs,connectionTimeoutMs, retryPolicy);
client.start();
return client;
}
}
添加注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExclusiveLock {
String key();
//是否可重入
boolean reentrant() default false;
//超时时间
long time() default 0L;
//时间单位
TimeUnit unit() default TimeUnit.SECONDS;
}
添加切面
import com.shall.annotation.ExclusiveLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
public class LockAspect {
@Autowired
CuratorFramework client;
private final String reentrantPrefix = "/shall/zkLock/reentrant";
private final ConcurrentMap<Thread,InterProcessLock> reentrantMap = new ConcurrentHashMap<>();
@Pointcut("@annotation(com.shall.annotation.ExclusiveLock)")
public void exclusiveLockPointCut(){}
/**
* 排他锁切面
* @param jointPoint
* @throws Throwable
*/
@Around("exclusiveLockPointCut()")
public void exclusiveLock(ProceedingJoinPoint jointPoint) throws Throwable {
Method method = ((MethodSignature) jointPoint.getSignature()).getMethod();
ExclusiveLock annotation = method.getAnnotation(ExclusiveLock.class);
String key = annotation.key();
long time = annotation.time();
TimeUnit unit = annotation.unit();
boolean reentrant = annotation.reentrant();
if (time == 0L){
unit = null;
}
InterProcessLock lock = null;
Thread thread = Thread.currentThread();
if (reentrant){
lock = reentrantMap.get(thread);
if (ObjectUtils.isEmpty(lock)){
//可重入
lock = new InterProcessMutex(client,reentrantPrefix + key);
reentrantMap.put(thread,lock);
}
}else {
//不可重入
lock = new InterProcessSemaphoreMutex(client,reentrantPrefix + key);
}
lock.acquire(time,unit);
log.info(thread.getName() + "获取到排他锁");
//执行代码
jointPoint.proceed();
lock.release();
if (reentrant){
InterProcessMutex lock1 = (InterProcessMutex)reentrantMap.get(thread);
if (!lock1.isOwnedByCurrentThread()) {
reentrantMap.remove(thread);
}
}
log.info(thread.getName() + "释放排他锁");
}
}
使用方式
方法上打注解,加上key,重入标记默认为false,可设置成true,不设置重入标记的话要注意不要嵌套使用,否则程序会卡住不动,最好使用超时时间,在注解中添加超时时间的标记,不添加为不超时
@Override
@ExclusiveLock(key = "exclusive1",reentrant = true)
public void doTest(String name) {
log.info(name + "开始上厕所");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(name + "上完厕所,拿纸");
}
@Override
@ExclusiveLock(key = "exclusive1",reentrant = true)
public void getPaper() {
log.info("获取到纸");
}
如果有什么意见欢迎直接留言沟通