Redis分布式可重入锁实现

2022-05-05 15:55:31 浏览数 (1)

可重入锁

一个线程可以多次获取该锁,说明这把锁是可重入的。下面我们来实现Redis分布式可重入锁。我们可以用线程的ThreadLocal变量存储当前持有锁的计数。

核心代码

下面的代码并不完善,只是提供了一个思路。

代码语言:javascript复制
@Component
public class RedisWithReentrantLock {

    @Autowired
    private RedisTemplate redisTemplate;


    private static final String REDIS_VALUE = "r_lock";

    //ThreadLocal存储,同线程持有锁(key)相同,计数 1
    private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();

    //相当于setnx   超时时间
    //这里还有个问题,业务中有可能持有锁的那段代码没有执行完,锁就超时了,这里需要设计一个看门狗线程,去监听线程是否持有锁
    private boolean _lock(String key) {
        return redisTemplate.opsForValue().setIfAbsent(key, REDIS_VALUE, 20, TimeUnit.SECONDS);
    }

    //释放锁
    private void _unlock(String key) {
        redisTemplate.delete(key);
    }

    //获取当前线程持有锁的Map, key为redis的key,value为重入的次数
    private Map<String, Integer> currentLockers() {
        Map<String, Integer> refs = lockers.get();
        if (refs != null) {
            return refs;
        }

        lockers.set(new HashMap<>());
        return lockers.get();
    }

    //可重入加锁
    public boolean lock(String key) {
        Map<String, Integer> refs = currentLockers();
        Integer refCnt = refs.get(key);
        if (refCnt != null) {
            //当前线程下,每获取一次,加一次计数
            refs.put(key, refCnt   1);
            return true;
        }
        boolean ok = this._lock(key);
        if (!ok) {
            return false;
        }

        refs.put(key, 1);
        return true;
    }

    //可重入释放锁
    public boolean unlock(String key) {
        Map<String, Integer> refs = currentLockers();
        Integer refCnt = refs.get(key);
        if (refCnt == null) {
            return false;
        }

        refCnt = refCnt - 1;
        if ((refCnt > 0)) {
            refs.put(key, refCnt);
        } else {
            refs.remove(key);
            this._unlock(key);
        }
        return true;
    }


}

测试

如上图:当线程Thread-14获得锁,可以获取多次。如果没有释放,主线程就获取不到锁。当然在平时写代码时,可以调整代码结构来避免使用可重入锁。

参考:

《Redis深度历险核心原理与引用实践》 –钱文品

0 人点赞