比如一个操作要修改的用户的状态,修改状态需要先读出用户的状态,在内存里进行修改,改完了再存回去。
如果这样的操作同时进行了,就会出现并发问题,因为读取和保存状态这两个操作不是原子的。
分布式锁:
分布式锁本质上要实现的目标就是在Redis里面占“坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就
只好放弃或者稍后再试。
占坑一般是使用setnx(set if not exists)指令,只允许被一个客户端占坑。先来先到,用完了在调用del指令释放。
>setnx lock:codehole true
OK
...do something critical...
>del lock:codehole
(integer) 1
但有个问题,如果逻辑执行到中间出现异常,可能会导致del指令没有被调用,这样就会陷入死锁,锁就得不到释放了。
这样我们可以拿到锁之后,再给锁加上一个过期时间,for example:5s
>setnx lock:codehole true
OK
>expire lock:codehole 5
...do something critical...
>del lock:codehole
(integer) 1
如果在setnx和expire之间服务器进程突然挂掉的,就会导致expire得不到执行,也会造成死锁。这个问题根源在于setnx和expire是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。
>set lock:codehole true ex 5 nx
OK
...do something critical...
>del lock:codehole
上面这个指令就是setnx和expire组合一起的原子指令,它就是分布式锁奥义的所在。
超时问题:
Redis的分布式锁解决超时问题,如果在加锁和释放锁之间的逻辑执行的太长,以至于超出了锁的超时限制,就会出现问题。因为
这时候第一个线程持有的锁过期了,临界区的逻辑还没有执行完,这个时候第二个线程就提前重新持有了这把锁,导致临界区代码不能得到严格的串行执行。
lua 脚本
if redis.call("get",keys[1] == argv[1] then
return redis.call("del",keys[1]))
else
return 0
end
可重入性:
可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。
public class RedisWithReentrantLock{
private ThreadLocal<Map<String,Integer>> lockers=new ThreadLocal<>();
private Jedis jedis;
public RedisWithReentrantLock(Jedis jedis){
this.jedis=jedis;
}
private boolean lock(String key){
return jedis.set(key,"","nx","ex",5L)
}
private void unlock(){
jedis.del(key);
}
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-=1;
if(refCnt>0){
refs.put(key,refCnt);
}else{
refs.remove(key);
this.unlock(key);
}
return true;
}
public static void main(String[] args){
Jedis jedis=new Jedis();
RedisWithReentrantLock redis=new RedisWithReentrantLock(jedis);
System.out.println(redis.lock("codehole"));
System.out.println(redis.lock("codehole"));
System.out.println(redis.unlock("codehole"));
System.out.println(redis.unlock("codehole"));
}
}