增加同步锁
This commit is contained in:
parent
e71711b4a0
commit
20670df44c
@ -0,0 +1,235 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.lock;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能同步任务本地锁管理器
|
||||||
|
* <p>自动识别调用者(类名+方法名)和参数,生成唯一锁key,无需手动指定锁类型</p>
|
||||||
|
*
|
||||||
|
* <p>使用示例:
|
||||||
|
* <pre>
|
||||||
|
* // 加锁(自动识别调用方法和参数)
|
||||||
|
* if (!syncLockManager.tryLock(externalSystemId)) {
|
||||||
|
* return; // 已有同步任务在执行
|
||||||
|
* }
|
||||||
|
* try {
|
||||||
|
* // 执行同步逻辑
|
||||||
|
* } finally {
|
||||||
|
* syncLockManager.unlock(externalSystemId);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>锁key格式:ClassName.methodName:param1:param2:...</p>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SyncLockManager {
|
||||||
|
|
||||||
|
// 统一的锁存储:key = ClassName.methodName:param1:param2:...
|
||||||
|
private final ConcurrentHashMap<String, Boolean> locks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试获取锁(自动识别调用者)
|
||||||
|
*
|
||||||
|
* @param params 锁参数(如 externalSystemId, viewId等)
|
||||||
|
* @return true-获取成功,false-已被锁定
|
||||||
|
*/
|
||||||
|
public boolean tryLock(Object... params) {
|
||||||
|
CallerInfo callerInfo = getCallerInfo();
|
||||||
|
String lockKey = buildLockKey(callerInfo, params);
|
||||||
|
boolean acquired = locks.putIfAbsent(lockKey, true) == null;
|
||||||
|
|
||||||
|
if (!acquired) {
|
||||||
|
log.warn("同步任务正在执行中,跳过本次同步: method={}, params={}",
|
||||||
|
callerInfo.getFullMethodName(), formatParams(params));
|
||||||
|
} else {
|
||||||
|
log.debug("获取同步锁成功: method={}, params={}",
|
||||||
|
callerInfo.getFullMethodName(), formatParams(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
return acquired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放锁(自动识别调用者)
|
||||||
|
*
|
||||||
|
* @param params 锁参数(必须与tryLock时完全一致)
|
||||||
|
*/
|
||||||
|
public void unlock(Object... params) {
|
||||||
|
CallerInfo callerInfo = getCallerInfo();
|
||||||
|
String lockKey = buildLockKey(callerInfo, params);
|
||||||
|
locks.remove(lockKey);
|
||||||
|
log.debug("同步任务完成,释放锁: method={}, params={}",
|
||||||
|
callerInfo.getFullMethodName(), formatParams(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查锁是否被持有(自动识别调用者)
|
||||||
|
*
|
||||||
|
* @param params 锁参数
|
||||||
|
* @return true-已被锁定,false-未锁定
|
||||||
|
*/
|
||||||
|
public boolean isLocked(Object... params) {
|
||||||
|
CallerInfo callerInfo = getCallerInfo();
|
||||||
|
String lockKey = buildLockKey(callerInfo, params);
|
||||||
|
return locks.containsKey(lockKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前锁数量(用于监控)
|
||||||
|
*
|
||||||
|
* @return 当前持有的锁数量
|
||||||
|
*/
|
||||||
|
public int getLockCount() {
|
||||||
|
return locks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取调用者信息(通过栈追踪,智能跳过Spring代理层)
|
||||||
|
*
|
||||||
|
* @return 调用者信息
|
||||||
|
*/
|
||||||
|
private CallerInfo getCallerInfo() {
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
|
||||||
|
// 从栈顶开始遍历,跳过本类和Spring的代理/拦截器类
|
||||||
|
// 栈结构示例:
|
||||||
|
// 0: Thread.getStackTrace()
|
||||||
|
// 1: SyncLockManager.getCallerInfo()
|
||||||
|
// 2: SyncLockManager.tryLock/unlock/isLocked()
|
||||||
|
// 3+: 可能是Spring代理类或真实调用者
|
||||||
|
|
||||||
|
for (int i = 3; i < stackTrace.length; i++) {
|
||||||
|
StackTraceElement element = stackTrace[i];
|
||||||
|
String className = element.getClassName();
|
||||||
|
String methodName = element.getMethodName();
|
||||||
|
|
||||||
|
// 跳过 Spring 和 JDK 的内部类/代理类
|
||||||
|
if (shouldSkipFrame(className, methodName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到真正的业务调用者
|
||||||
|
String simpleClassName = className.substring(className.lastIndexOf('.') + 1);
|
||||||
|
return new CallerInfo(simpleClassName, methodName, className + "." + methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底:使用unknown
|
||||||
|
return new CallerInfo("Unknown", "unknown", "Unknown.unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否应该跳过该栈帧
|
||||||
|
*
|
||||||
|
* @param className 类名
|
||||||
|
* @param methodName 方法名
|
||||||
|
* @return true-跳过,false-不跳过
|
||||||
|
*/
|
||||||
|
private boolean shouldSkipFrame(String className, String methodName) {
|
||||||
|
// 跳过 CGLIB 动态代理类(所有包含$$的类都是CGLIB代理)
|
||||||
|
if (className.contains("$$")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过 JDK 动态代理
|
||||||
|
if (className.startsWith("jdk.proxy") || className.startsWith("com.sun.proxy")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过 Spring AOP 拦截器
|
||||||
|
if (className.contains("AsyncExecutionInterceptor") ||
|
||||||
|
className.contains("TransactionInterceptor") ||
|
||||||
|
className.contains("CglibAopProxy") ||
|
||||||
|
className.contains("ReflectiveMethodInvocation") ||
|
||||||
|
className.contains("DynamicAdvisedInterceptor")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过线程池和任务执行器
|
||||||
|
if (className.contains("ThreadPoolExecutor") ||
|
||||||
|
className.contains("FutureTask") ||
|
||||||
|
className.contains("CompletableFuture")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过 Java 反射
|
||||||
|
if (className.startsWith("java.lang.reflect") ||
|
||||||
|
className.startsWith("jdk.internal.reflect")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过 lambda 表达式的合成方法
|
||||||
|
if (methodName.contains("lambda$")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建锁的唯一key
|
||||||
|
*
|
||||||
|
* @param callerInfo 调用者信息
|
||||||
|
* @param params 参数列表
|
||||||
|
* @return 锁key
|
||||||
|
*/
|
||||||
|
private String buildLockKey(CallerInfo callerInfo, Object... params) {
|
||||||
|
StringBuilder key = new StringBuilder(callerInfo.getFullMethodName());
|
||||||
|
for (Object param : params) {
|
||||||
|
key.append(":").append(param != null ? param : "null");
|
||||||
|
}
|
||||||
|
return key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化参数用于日志输出
|
||||||
|
*
|
||||||
|
* @param params 参数列表
|
||||||
|
* @return 格式化字符串
|
||||||
|
*/
|
||||||
|
private String formatParams(Object... params) {
|
||||||
|
if (params.length == 0) {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("[");
|
||||||
|
for (int i = 0; i < params.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.append(params[i]);
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用者信息
|
||||||
|
*/
|
||||||
|
private static class CallerInfo {
|
||||||
|
private final String simpleClassName;
|
||||||
|
private final String methodName;
|
||||||
|
private final String fullMethodName;
|
||||||
|
|
||||||
|
public CallerInfo(String simpleClassName, String methodName, String fullMethodName) {
|
||||||
|
this.simpleClassName = simpleClassName;
|
||||||
|
this.methodName = methodName;
|
||||||
|
this.fullMethodName = fullMethodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSimpleClassName() {
|
||||||
|
return simpleClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethodName() {
|
||||||
|
return methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullMethodName() {
|
||||||
|
return fullMethodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user