可重入锁
一个线程可以多次获取该锁,说明这把锁是可重入的。下面我们来实现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深度历险核心原理与引用实践》 –钱文品