从零构建高并发锁工具:基于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锁 ⭐⭐ 强一致性场景 性能最低

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

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

相关推荐
专注API从业者20 分钟前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
摇滚侠37 分钟前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY1 小时前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克32 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠2 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌2 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包
Agent产品评测局3 小时前
生产排期与MES/ERP系统打通,实操方法详解 —— 2026企业级智能体自动化选型与实战指南
java·运维·人工智能·ai·chatgpt·自动化
阿丰资源3 小时前
基于Spring Boot的电影城管理系统(直接运行)
java·spring boot·后端
呱牛do it3 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 8)
java
消失的旧时光-19434 小时前
Spring Boot 工程化进阶:统一返回 + 全局异常 + AOP 通用工具包
java·spring boot·后端·aop·自定义注解