从零构建高并发锁工具:基于AOP与ReentrantLock的轻量级分布式锁实践

本文皆为个人原创,请尊重创作,未经许可不得转载。

本文将详细解析一个高效、可重入的JVM级锁工具实现,包括核心锁工具类、注解式声明锁以及AOP切面实现,助你掌握Java并发编程的高级技巧。

一、锁工具的核心需求

为什么需要自研分布式锁工具?​

在分布式系统中,跨进程资源竞争常依赖Redis/ZooKeeper等中间件。但在单服务高并发场景 ​(如订单处理、库存扣减)中,JVM级别的锁工具能提供更高性能(避免网络IO)和更低延迟。LockUtil正是针对这一场景的轻量级解决方案

在多线程环境下且单机服务,需要一种机制来保证资源的安全访问。一个完善的锁工具应该具备:

  1. 可重入性:同一线程可多次获取同一把锁
  2. 公平性选择:支持公平锁与非公平锁
  3. 锁清理机制:自动清理闲置锁,避免内存泄漏
  4. 超时控制:防止死锁
  5. 易用性:简洁的API和注解支持

二、核心组件解析

1. LockUtil:锁工具核心类

java 复制代码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
/**
 * JVM级锁工具
 *
 * @Author:Derek_Smart
 * @Date:2025/7/10 14:06
 */
public class LockUtil implements AutoCloseable {
    // 使用ConcurrentHashMap存储锁对象,键为锁标识
    private final ConcurrentHashMap<String, LockHolder> lockMap = new ConcurrentHashMap<>();
    
    // 清理线程池
    private final ScheduledExecutorService cleaner;
    private final boolean fair; // 是否公平锁
    private final long cleanInterval; // 清理间隔(毫秒)
    private final long idleTimeout; // 空闲超时(毫秒)
    private volatile boolean closed = false;
    
    // 构造方法
    public LockUtil(boolean fair, long cleanInterval, long idleTimeout, TimeUnit unit) {
        this.fair = fair;
        this.cleanInterval = unit.toMillis(cleanInterval);
        this.idleTimeout = unit.toMillis(idleTimeout);
        
        this.cleaner = Executors.newSingleThreadScheduledExecutor(
            r -> new Thread(r, "lock-cleaner-daemon")
        );
        startCleaner();
    }
    
    // 启动清理任务
    private void startCleaner() {
        cleaner.scheduleAtFixedRate(
            this::safeCleanup,
            cleanInterval, cleanInterval, TimeUnit.MILLISECONDS
        );
    }
    
    // 安全清理方法
    private void safeCleanup() {
        long now = System.currentTimeMillis();
        lockMap.entrySet().removeIf(entry -> {
            LockHolder holder = entry.getValue();
            return !holder.isInUse() && 
                   (now - holder.getLastAccessTime() > idleTimeout);
        });
    }
    
    // 加锁并执行操作(核心方法)
    public <T> T execute(String key, long waitTime, Callable<T> action) throws Exception {
        LockHolder holder = lockMap.compute(key, (k, v) -> 
            (v == null) ? new LockHolder(fair) : v
        );
        
        if (!holder.acquireLock(waitTime, TimeUnit.MILLISECONDS)) {
            throw new TimeoutException("Lock timeout for: " + key);
        }
        
        try {
            return action.call();
        } finally {
            holder.releaseLock();
        }
    }
    
    // 内部锁持有者类
    private class LockHolder {
        private final ReentrantLock lock;
        private final AtomicInteger holdCount = new AtomicInteger(0);
        private volatile long lastAccessTime;
        
        LockHolder(boolean fair) {
            this.lock = new ReentrantLock(fair);
            updateAccessTime();
        }
        
        // 获取锁
        boolean acquireLock(long timeout, TimeUnit unit) throws InterruptedException {
            if (timeout <= 0) {
                lock.lock(); // 无限等待模式
                holdCount.incrementAndGet();
                updateAccessTime();
                return true;
            } else {
                boolean acquired = lock.tryLock(timeout, unit);
                if (acquired) {
                    holdCount.incrementAndGet();
                    updateAccessTime();
                }
                return acquired;
            }
        }
        
        // 释放锁
        void releaseLock() {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                int count = holdCount.decrementAndGet();
                if (count == 0) updateAccessTime();
            } else {
                throw new IllegalMonitorStateException();
            }
        }
        
        // 检查锁是否被占用
        boolean isInUse() {
            return holdCount.get() > 0 || lock.isLocked();
        }
    }
}

关键设计解析:

  1. 锁存储结构 :使用ConcurrentHashMap存储锁对象,键为锁标识

  2. 自动清理机制

    • 定时清理空闲超时的锁(safeCleanup方法)
    • 使用ScheduledExecutorService定期执行清理
    • 仅清理未被使用且超过空闲超时时间的锁
  3. 锁获取策略

    • waitTime <= 0时使用无限等待模式
    • 支持超时等待,避免死锁
  4. 资源管理 :实现AutoCloseable接口,正确关闭线程池

核心架构设计解析

1. ​三级锁管理模型
java 复制代码
// 锁管理层级关系
ConcurrentHashMap<String, LockHolder>  // 全局锁池(Key: 业务标识)
    └── LockHolder                     // 锁持有者(封装ReentrantLock)
        ├── ReentrantLock              // 物理锁(支持公平/非公平)
        └── AtomicInteger holdCount    // 重入计数器
  • 动态锁创建 :通过compute()实现锁的懒加载,避免预分配资源浪费
  • 双重状态跟踪holdCount记录重入次数 + isLocked()检测锁占用,确保清理安全性
2. ​空闲锁自动回收机制
java 复制代码
cleaner.scheduleAtFixedRate(this::safeCleanup, cleanInterval, TimeUnit.MILLISECONDS);
  • 智能清理策略 :仅回收未被占用超时 的锁(!holder.isInUse() && (now - lastAccessTime > idleTimeout)
  • 避免误删重入锁 :通过holdCount > 0判断锁是否真正空闲
3. ​ReentrantLock的精细化控制
java 复制代码
// 支持公平/非公平锁选择
public LockUtil(boolean fair, long cleanInterval, long idleTimeout, TimeUnit unit) {
    this.fair = fair; // true=公平锁, false=非公平锁
}
  • 公平锁:按请求顺序获取,避免线程饥饿
  • 非公平锁:允许插队,吞吐量更高(默认选择)

2. WithLock:声明式锁注解

java 复制代码
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
 * @Author:Derek_Smart
 * @Date:2025/7/10 14:33
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WithLock {
    /** 
     * 锁Key(支持SpEL表达式,如:`'order:' + #orderId`) 
     */
    String key() default "";
    
    /** 
     * 最大等待时间(默认不等待) 
     */
    long waitTime() default 0;
    
    /** 
     * 时间单位(默认毫秒) 
     */
    TimeUnit unit() default TimeUnit.MILLISECONDS;
}

3. LockAspect:注解实现切面

java 复制代码
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.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
 * @Author:Derek_Smart
 * @Date:2025/7/10 14:34
 */
@Aspect
@Component
@Slf4j
public class LockAspect {
    private final LockUtil lockUtil = new LockUtil();
    private final ExpressionParser parser = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    
    @Around("@annotation(withLock)")
    public Object around(ProceedingJoinPoint pjp, WithLock withLock) throws Throwable {
        // 解析锁Key(支持SpEL动态参数)
        String key = resolveKey(pjp, withLock.key());
        long waitTime = withLock.waitTime();
        TimeUnit unit = withLock.unit();
        
        return lockUtil.execute(key, unit.toMillis(waitTime), () -> {
            try {
                return pjp.proceed();
            } catch (Throwable e) {
                log.error("LockAspect 加锁失败", e);
                throw new RuntimeException(e);
            }
        });
    }
    
    /**
     * 解析SpEL表达式生成动态Key
     */
    private String resolveKey(ProceedingJoinPoint pjp, String keyExpr) {
        if (keyExpr.isEmpty()) {
            // 默认Key:类名+方法名
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            return signature.getDeclaringTypeName() + "." + signature.getName();
        }
        
        // 解析SpEL表达式
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Object[] args = pjp.getArgs();
        MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(
            null, method, args, nameDiscoverer
        );
        Expression exp = parser.parseExpression(keyExpr);
        return exp.getValue(context, String.class);
    }
}

关键功能解析:

  1. SpEL表达式支持:动态生成锁键

    • 支持从方法参数中提取值
    • 示例:@WithLock(key = "'user:' + #userId")
  2. 默认键策略:类名+方法名作为默认锁键

  3. 异常处理:捕获并记录加锁过程中的异常

  4. 无缝集成:通过AOP实现业务逻辑与锁管理的解耦

三、使用示例

基本使用

java 复制代码
// 创建锁工具实例
LockUtil lockUtil = new LockUtil(true, 5, 30, TimeUnit.MINUTES);

try {
    lockUtil.execute("resource_key", 1000, () -> {
        // 需要加锁执行的业务逻辑
        System.out.println("执行关键操作");
        return null;
    });
} catch (Exception e) {
    e.printStackTrace();
}

注解式使用

java 复制代码
@Service
public class OrderService {
    
    @WithLock(key = "'order:' + #orderId", waitTime = 2, unit = TimeUnit.SECONDS)
    public void updateOrder(String orderId, Order newOrder) {
        // 更新订单逻辑
    }
    
    @WithLock // 使用默认键(类名+方法名)
    public void processBatch() {
        // 批量处理逻辑
    }
}

四、设计亮点

  1. 智能锁清理

    • 使用守护线程定期清理
    • 基于最后访问时间和使用状态判断
    • 防止内存泄漏
  2. 动态锁键生成

    • 支持SpEL表达式
    • 基于方法参数动态生成
    • 默认类名+方法名策略
  3. 可重入支持

    • 通过holdCount计数器实现
    • 确保同一线程可多次获取同一锁
  4. 资源安全管理

    • 实现AutoCloseable
    • 正确关闭线程池
    • 防止线程泄漏
  5. 灵活的超时控制

    • 支持不同时间单位
    • 无限等待模式(waitTime <= 0)
    • 精确的超时异常处理

五、性能优化建议

  1. 锁粒度控制
java 复制代码
// 细粒度锁 - 推荐
@WithLock(key = "'account:' + #accountId")

// 粗粒度锁 - 谨慎使用
@WithLock(key = "'all_accounts'")
  1. 超时设置

    • 根据业务场景设置合理超时
    • 避免过长(浪费资源)或过短(频繁超时)
  2. 锁统计监控

java 复制代码
// 监控活跃锁数量
int activeLocks = lockUtil.activeLockCount();

// 监控总锁数量
int totalLocks = lockUtil.totalLockCount();
  1. 公平性选择

    • 公平锁(fair=true):保证顺序,性能较低
    • 非公平锁(fair=false):吞吐量高,可能饥饿

六、扩展思考

  1. 分布式锁扩展

    • 可替换LockUtil实现为Redis或Zookeeper方案
    • 保持API不变,实现无缝迁移
  2. 锁降级/升级

    • 增加读写锁支持
    • 实现锁的降级(写锁→读锁)
  3. 锁监控

    • 添加JMX支持
    • 实时监控锁状态
  4. 性能统计

    • 记录锁等待时间
    • 统计锁竞争情况

七、总结

本文详细解析了一个功能完备的JVM级锁工具实现,它具有以下特点:

  1. 功能完善:支持可重入、公平锁选择、超时控制等
  2. 资源安全:实现自动清理和资源关闭
  3. 使用便捷:提供注解式声明锁支持
  4. 动态灵活:支持SpEL表达式生成锁键
  5. 高性能:合理设计避免性能瓶颈

适用场景推荐​:

  • 电商库存扣减
  • 财务批次记账
  • 本地缓存更新
  • 短任务排队处理

🔍​与其他方案对比

方案 吞吐量 适用场景 缺点
LockUtil ⭐⭐⭐⭐ 单服务高并发 不适用分布式集群
Redis分布式锁 ⭐⭐⭐ 跨进程资源竞争 网络延迟影响性能
synchronized ⭐⭐ 简单临界区保护 功能扩展性差
ZooKeeper锁 ⭐⭐ 强一致性场景 性能最低

该锁工具适用于单机环境下的资源同步控制,通过合理的锁粒度控制和超时设置,可以在保证线程安全的同时获得良好的性能表现。

本文皆为个人原创,请尊重创作,未经许可不得转载。

相关推荐
C4程序员7 分钟前
北京JAVA基础面试30天打卡06
java·开发语言·面试
很小心的小新21 分钟前
五、SpringBoot工程打包与运行
java·spring boot·后端
ACGkaka_24 分钟前
SpringBoot 集成 MapStruct
java·spring boot·后端
代码哲学系1 小时前
第一阶段:Java基础入门④Java核心API
java·强化学习
北京_宏哥2 小时前
《刚刚问世》系列初窥篇-Java+Playwright自动化测试-33-JavaScript的调用执行-上篇 (详细教程)
java·前端·javascript
自由的疯2 小时前
java程序员怎么从Python小白变成Python大拿?(七)
java·后端·trae
风的归宿552 小时前
log4j2异步模式源码解析
java·后端
javadaydayup2 小时前
3 个案例看透 Spring @Component 扫描:从普通应用到 Spring Boot
spring boot·后端·spring
种子q_q2 小时前
Java中的代理模式
java·后端·面试
桦说编程2 小时前
一文帮你掌握集合类库常见工具方法
java·后端·性能优化