增加同步锁

This commit is contained in:
dengqichen 2025-12-01 18:08:12 +08:00
parent e71711b4a0
commit 20670df44c

View File

@ -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;
}
}
}