最近在看《Redis 深度历险》这本书,我们都知道Redis用来实现分布式锁是一种常用的技术方案,本质上 就是用命令setnx ex来占据一个坑位。在Java里面体现出来可能是这样一行代码

jedis.set(lockKey, lockValue, "NX", "PX", expireTime);

但是这样在加锁方式是不支持线程重入的,即同一个线程在持有锁的情况下再次请求加锁,此时是会失败 的。需要在set外层加一个包装,使其支持可重入。既然需要同一个线程再次持有,那么往往都需要引入 一个ThreadLocal<>。我先实现一个简单版的,在ThreadLocal里保存当前线程的线程id。如下代码

package deregister;

import redis.clients.jedis.Jedis;

public class ReentrantLockRedis {

    private ThreadLocal<Long> threadId = new ThreadLocal<>();
    private Jedis jedis;

    public ReentrantLockRedis(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean lock(String key) {
        Long id = threadId.get();
        if (id == null) {
            threadId.set(Thread.currentThread().getId());
            return jedis.set(key, "", "NX", "PX", 30) != null;
        }
        return Thread.currentThread().getId() == id;
    }

    public boolean unlock(String key) {
        Long id = threadId.get();
        if (id != null) {
            threadId.remove();
            Long count = jedis.del(key);
            return count == 1;
        }
        return false;
    }

}

上述代码其实就能实现分布式锁的可重入。下面我们来看一下稍微复杂一点的,他在ThreadLocal里面 保存一个Map,即每个线程都持有一个自己的Map;Map的key就是Redis的key,value是这个key被 获取的次数。代码如下,其实也挺容易理解的。不过这本书的作者不建议在Redis操作的外层封装,来支持 线程重入,应该在业务角度考虑,是否有其他替代方案。原话为

它加重了客户端的复杂性,在编写业务方法时注意在逻辑结构上进行调整完全可以不使用可重入锁

package deregister;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;

public class ReentrantLockRedisMore {

    private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();
    private Jedis jedis;

    public ReentrantLockRedisMore(Jedis jedis) {
        this.jedis = jedis;
    }

    public Map<String, Integer> currentLockers() {
        Map<String, Integer> map = lockers.get();
        if (map != null) {
            return map;
        }
        lockers.set(new HashMap<>());
        return lockers.get();
    }

    public boolean _lock(String key) {
        return jedis.set(key, "", "NX", "PX", 30) != null;
    }

    public void _unlock(String key) {
        jedis.del(key);
    }

    public boolean lock(String key) {
        Map<String, Integer> lockers = currentLockers();
        Integer count = lockers.get(key);
        if (count != null) {
            lockers.put(key, count + 1);
            return true;
        }
        boolean _ok = _lock(key);
        if (!_ok) {
            return false;
        }
        lockers.put(key, 1);
        return true;
    }

    private boolean unlock(String key) {
        Map<String, Integer> lockers = currentLockers();
        Integer count = lockers.get(key);
        if (count == null) {
            return false;
        }
        count--;
        if (count > 0) {
            lockers.put(key, count);
        } else {
            lockers.remove(key);
            _unlock(key);
        }
        return true;
    }

}