在程序中使用setnx实现分布式锁或许是可行的

来源:4-4 如何实现分布式锁

mapper

2019-08-14

老师好,

按照先setnx然后再expire的确存在没有执行到expire引发的独占锁的问题。不过在程序中我们可以只需要setnx一条原子性的命令即可实现,思路如下:

setnx的key为请求标识(requestId),value为该key的过期时间戳。我们可以根据该时间戳来解决expireTime的问题,总的来说就是根据时间来判断锁是否过期以及是否进入死锁状态。代码如下

    /**
     * 加锁
     * @param key    seckillId
     * @param value  当前时间+超时时间
     * @return
     */
    public boolean lock(String key, String value) {
        // 可以设置返回true (setnx)
        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, value);
        if (isLock) {
            return true;
        }
        String currentValue = redisTemplate.opsForValue().get(key);
        // 如果锁已经过期
        if (!StringUtils.isEmpty(currentValue)
                && Long.valueOf(currentValue) < System.currentTimeMillis()) {
            // 获取上一个锁的时间,并设置新锁的时间
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
            if (!StringUtils.isEmpty(oldValue)
                    && oldValue.equals(currentValue)) {
                log.info("旧锁已过期,本次加锁成功");
                return true;
            }
        }
        return false;
    }

然后我们在解锁的时候将该key给清除,即使解锁的时候出现了错误,因为时间戳的缘故,也不会进入死锁的状态。

不知道这种方案是否可行,请老师指点一下


extra
  在评论区看到了一些恶意评论,其实有些评论都是其他平台请的水军,故意刷的(很多都是同一个人),感觉有点搞人心态… 其实不必在意这些;至少对我个人来说,收获还是很大的。

写回答

1回答

翔仔

2019-08-15

同学好,同学的思路很不错,多思考能成长得很快,这个方法如果针对单线程还可以(但是失去了分布式锁的意义),如果是多个线程的话,可能还是有问题的,因为会有同一时刻两个线程都返回true的情况,比如线程1执行到

String oldValue = redisTemplate.opsForValue().getAndSet(key, value);

而线程2执行到

String currentValue = redisTemplate.opsForValue().get(key);

就会拿到线程1的currentValue,

此时两个线程的

            if (!StringUtils.isEmpty(oldValue)
                    && oldValue.equals(currentValue))

都成立:)

就会有隐患

特别感谢同学对翔仔的支持,让翔仔觉得很欣慰,我会用我的热情和坚持来服务好尽可能多的同学:)

0
7
翔仔
回复
慕UI3469488
:)多线程有很多不确定的因素在里面,所以线程不安全的逻辑最好不要引入到多线程里
2019-08-20
共7条回复

剑指Java面试-Offer直通车 百度资深面试官授课

招聘季即将到来,让百度资深面试官来为你的高薪Offer保驾护航

8441 学习 · 1872 问题

查看课程