Reentrant lock
A thread can acquire the lock multiple times, indicating that the lock is reentrant. Let's implement Redis
distributed reentrant locks. We can use thread ThreadLocal
variables to store the current lock count.
Core code
The following code is not perfect, but provides an idea.
@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;
}
}
test
As shown above: when a thread Thread-14
acquires a lock, it can acquire it multiple times. If it is not released, the main thread cannot acquire the lock. Of course, when writing code, you can adjust the code structure to avoid using reentrant locks.
reference:
"The Core Principles and Citation Practice of Redis In-Depth Adventure"-Qian Wenpin