Java分布式锁(一行代码搞定)

本文介绍了一种使用Redis实现的一行代码搞定的Java分布式锁方案。该方案通过自定义的LockTemplate简化了分布式锁的使用,使得开发者能够更专注于业务逻辑而非锁的实现细节。文章还提供了具体的Spring Boot项目示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java分布式锁(一行代码搞定)

前面几篇文章都是介绍的java juc lock包单机锁,但是目前很多应用都是分布式的(不同jvm进程),单机锁已经不能满足应用的需求了。
网上有关分布式锁的文章很多有基于memcached,redis,zookeeper,但感觉直接拿到线上使用,供公司内部使用还是差点火候,于是就一行代码搞定分布式锁文章就诞生了。

现在几乎每个应用都会用redis,我们就以redis为例使用分布式锁:
先看在springboot项目中使用方式:

lockTemplate.doBiz(LockedCallback<R> lockedCallback,String scene,String uniqueId,String key,Long expireSecond)

这样一来你只需要在回调类lockedCallback写你的业务逻辑,极大地简化了使用分布式锁的使用,能让你更加专注你的业务逻辑,集中管理所有的锁,而不是项目中每个人一套(而且并不是所有人都能用对)。

下面是我做的测试:

@RestController
@RequestMapping(path="/monitor")
public class MonitorController {

    @Autowired
    private LockTemplate<String> lockTemplate;
    private ExecutorService pool = Executors.newFixedThreadPool(10);
    /**
     * @description
     * @param
     * @return
     * @date: 2018/6/12 下午5:18
     * @author:yzMa
     */
    @GetMapping("/lock/go")
    public ApiResult<Object> lockTest(){
        for (int i = 0; i < 6; i++) {
            pool.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return doSomething();
                }
            });
        }
        return ApiResult.buildSuccess(null);
    }

    /**
     * @description 竞态条件
     * @param 
     * @return 
     * @date: 2018/6/12 下午6:15
     * @author:yzMa
     */
    private String doSomething(){
        return lockTemplate.doBiz(new LockedCallback<String>() {
            @Override
            public String callback() {

                try {
        System.out.println(Thread.currentThread().getName()+" 成功获取到了锁开始执行逻辑");
                    Thread.sleep(4000);
                    System.out.println(Thread.currentThread().getName()+" 成功获取到了锁开始执行逻辑结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "aa";
            }
        },"testLock-scene","aaaa-uniqueId","aaaa",5L);
    }
}

受到网上前辈的影响,使用redis.set(key,val,expire,nx)命令加锁,使用lua脚本执行解锁,至于为什么自行百度下(多笔操作这个失败了,那个超时了,加锁了释放锁失败了如何处理等等),我也是踩过很多坑,分析过很多次才有了这个线上版本。

分布式锁模板类

@Slf4j
@Component
public class LockTemplate<R> {

    @Resource(name = "luaRedisDistributeLock")
    private DistributeLock distributeLock;
    public <R> R doBiz(LockedCallback<R> lockedCallback,String scene,String uniqueId,String key,Long expireSecond){
        if(StringUtils.isBlank(uniqueId)){
            log.info("|doBiz no uniqueId use serialize lock (变为分布式串行锁)");
            uniqueId = key+System.currentTimeMillis();
        }
        boolean acquiredSuccess = false;
        try{
            acquiredSuccess = distributeLock.tryLock(key, uniqueId, expireSecond);
            if(!acquiredSuccess){
                log.info("|doBiz acquire lock fail scene={}, key={},uniqueId={},expireSecond={}",scene,key,uniqueId,expireSecond);
                throw new BusinessException(ApiResult.ACQUIRE_LOCK_FAIL,ApiResult.ACQUIRE_LOCK_FAIL_MSG);
            }
            log.info("|doBiz acquire lock success scene={}, key={},uniqueId={},expireSecond={}",scene,key,uniqueId,expireSecond);
            return (R)lockedCallback.callback();

        }catch (Exception e){
            if(! (e instanceof BusinessException)){
                log.error("|doBiz|lockedCallback not BusinessException, scene={},key={},uniqueId={},expireSecond={} exception e:",scene,key,uniqueId,expireSecond,e);
            }
            throw e;
        }finally {
            if(acquiredSuccess){
                distributeLock.unlock(key,uniqueId);
                log.info("|doBiz release lock success scene={}, key={},uniqueId={},expireSecond={}",scene,key,uniqueId,expireSecond);
            }
        }
    }

    // 分布式锁模板,大锁 没有并发
    public <R> R doBiz(LockedCallback<R> lockedCallback,String scene,String key,Long expireSecond){

            return doBiz(lockedCallback,scene,"",key,expireSecond);
    }
}

lua分布式锁实现类

@Component("luaRedisDistributeLock")
public class LuaRedisDistributeLock implements DistributeLock{

    private String unlockLua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();

    class ExpirationSub extends Expiration{
        long expirationTime;
        TimeUnit timeUnit;
        public ExpirationSub(long expirationTime,TimeUnit timeUnit){
            super(expirationTime,timeUnit);
        }
    }

    @Override
    public void lock(String key, String uniqueId, Long expireTime) {
        throw new RuntimeException("lua redis not support for now");
    }

    @Override
    public boolean tryLock(String key,String uniqueId, Long expireSeconds) {
        Assert.notNull(key,"redis key 不能为空");
        Assert.notNull(uniqueId,"uniqueId 不能为空");
        Assert.notNull(expireSeconds,"expireTime 不能为空");
        Assert.isTrue(expireSeconds > 0 && expireSeconds <= 10 ,"锁的过期时间范围介于(0,10]秒");
        Boolean lockedSuccess = stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
            return redisConnection.set(key.getBytes(), val, expirationSub, RedisStringCommands.SetOption.SET_IF_ABSENT);//老版本
            }
        });

        if(lockedSuccess){
            return true;
        }
        return false;
    }

    public void unlock(String key, String uniqueId) {

        //使用Lua脚本删除Redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
        //spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
        stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                Object nativeConnection = redisConnection.getNativeConnection();

                //集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                Long luaResult = 0L;
                if (nativeConnection instanceof JedisCluster) {
                    luaResult =  (Long)((JedisCluster) nativeConnection).eval(unlockLua, Collections.singletonList(key), Collections.singletonList(uniqueId));
                }
                if(nativeConnection instanceof Jedis){
                    luaResult =  (Long)((Jedis)nativeConnection).eval(unlockLua, Collections.singletonList(key), Collections.singletonList(uniqueId));
                }
                return luaResult != 0;
            }
        });
    }
}

分布式锁接口类

public interface DistributeLock {
    void lock(String key,String uniqueId,Long expireTime);
    boolean tryLock(String key,String uniqueId,Long expireTime);
    void unlock(String key,String uniqueId);
}

业务回调类

public interface LockedCallback<R> {
    R callback();
}

redisTemplate配置类

@Bean("cardRedisTemplate")
    public <String,V> RedisTemplate<String,V> cardRedisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String,V> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        //定义key序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();//Long类型会出现异常信息;需要我们上面的自定义key生成策略,一般没必要
        template.setKeySerializer(stringRedisSerializer);

        //定义value的序列化方式
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        return template;
    }

在你写分布式锁的时候最好先看下这篇文章redis分布式锁的正确姿势
考虑好这些问题之后再开始动手写。

<think>好的,我需要解释功能点估算法的概念,用户是刚开始学计算机的大学生,所以要用通俗易懂的语言,最好结合他们熟悉的场景。 首先,功能点估算法的定义是什么?应该是一种通过评估软件功能数量来预测项目时间和成本的方法。要避免专业术语,用他们能理解的例子,比如开发一个APP或者网站。 接下来,可能需要分解功能点的组成部分,比如输入、输出、数据存储等。可以类比点外卖或者购物车功能,这样更贴近他们的生活经验。 然后,计算步骤。要简化流程,用步骤列表的方式,可能加上计算公式的例子,比如调整因子和复杂度权重。但要注意别太复杂,保持简单。 还要比较功能点估算法和其他方法的区别,比如和三点估计法对比,或者和代码行数估算。强调功能点的优势,比如不依赖编程语言,适合早期估算。 可能还要举一个具体的例子,比如开发一个学生选课系统,拆解各个功能点,计算总点数,再转换成时间或成本。这样用户更容易理解。 最后,提醒注意事项,比如需求不明确时可能不适用,或者需要经验调整参数。可以加入学长学姐的经验教训,让内容更生动。 需要检查有没有用中文,是否遵循了之前的格式要求,比如数学公式用$$,结构清晰,分点说明。确保没有使用Markdown,保持口语化但专业。</think>(学长打开IDE边敲伪代码边解释)功能点估算法就像给你的项目**做CT扫描**——把系统拆解成可量化的「功能器官」来评估工作量,比单纯数代码行科学多了!👨💻 举个你马上能理解的场景:开发「校园二手书交易平台」 **传统菜鸟估算**: 「这玩意应该和淘宝差不多,2个月能搞定吧?」(结果发现支付接口集成直接卡死) **功能点估算法**: 1️⃣ **解剖系统功能**(像拆分微服务): ```python # 功能组件清单 functions = [ {"类型": "用户注册", "输入项": 5}, # 学号/密码/验证码等 {"类型": "商品发布", "输出项": 3}, # 书籍图片/详情/价格 {"类型": "搜索功能", "查询项": 2}, # 书名/ISBN {"类型": "订单系统", "数据表": 4}, # 用户表/商品表/订单表/消息表 {"类型": "支付接口", "外部接口": 1} # 支付宝API ] ``` 2️⃣ **计算未调整功能点(UFP)**: ```markdown | 功能类型 | 数量 | 权重 | 小计 | |---------|-----|-----|-----| | 输入项 | 5 | ×4 | 20 | | 输出项 | 3 | ×5 | 15 | | 查询项 | 2 | ×4 | 8 | | 数据表 | 4 | ×10 | 40 | | 外部接口 | 1 | ×7 | 7 | | **总计** | | | 90 | ``` 3️⃣ **复杂度调整**(像给代码评Code Quality): - 分布式部署需要? ×1.2 - 要兼容移动端? ×1.1 - 首次对接支付系统? ×1.3 - **调整后功能点 = 90 × (1.2×1.1×1.3) ≈ 154** 4️⃣ **转换工作量**(根据团队生产力): - 如果团队1个功能点≈3人天 - **总工时 = 154 × 3 = 462人天** → 约4个月(5人团队) 💡 **计算机学生秒懂的3个对比**: ```java // vs 代码行估算 int java代码行 = 5000; double 成本 = 代码行 × 单价; // 但同样功能Python可能只要1000行 // vs 三点估算法 功能点法:自底向上拆解需求 → 像递归算法 三点法:整体预估 → 像黑盒测试 // vs 敏捷故事点 功能点:客观测量需求体积 → 像计算文件MD5 故事点:主观评估难度 → 像压缩率不确定的zip文件 ``` ⚠️ **防坑指南**(含学长实战翻车案例): 1. 不要漏「隐形成本」→ 就像忘记处理时区导致订单时间戳错误 - 示例:算上了支付接口,但没算「退款流程」功能点 2. 权重系数要动态调整 → 类似机器学习调参 - 新手团队:数据表权重从10→12(因为不熟悉ORM框架) 3. 遇到模糊需求时 → 像处理未初始化的变量 - 对策:先按最小功能集计算,再×风险系数(类似蒙特卡洛模拟) 🔧 **常用工具链**(提前装在简历里): - COSMIC功能点分析 → 像用Pylint做代码规范 - FiSMA方法 → 类似设计模式中的策略模式 - 用例点估算 → 像UML图转代码框架 🎯 **终极价值**: 从「老板说要做个微信」的懵逼状态,进化成「拆解出128个功能点+精确到人天」的靠谱工程师!(下次课程设计答辩甩出功能点分析表,直接震慑全场)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值