分布式锁

redis

setnx

非原子操作,SET if Not eXists, 如果不存在,则 SET,具有互斥性。

  • 加锁命令:SETNX key value,当键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名。
  • 解锁命令:DEL key,通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁。
  • 锁超时:EXPIRE key timeout, 设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住。

加锁实现

public boolean tryLock_with_set(String key, String UniqueId, int seconds) {
    return "OK".equals(jedis.set(key, UniqueId, "NX", "EX", seconds));
}

释放锁实现

不能直接用del key这种粗暴的方式,因为直接del key任何客户端都可以进行解锁了,所以解锁时,我们需要判断锁是否是自己的,基于value值来判断

Lua脚本的方式,尽量保证原子性。

public boolean releaseLock_with_lua(String key,String value) {
    String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
            "return redis.call('del',KEYS[1]) else return 0 end";
    return jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)).equals(1L);
}

redisson

锁续期

  1. 锁自动续期, 如果业务时间长,会自动续上30s
  2. 加锁业务完成后,不会给锁续期。即时不执行解锁代码, 也可以解锁

超时时间

  • 如果传递了锁的超时时间, 就发送给redis执行脚本,进行占锁
  • 如果未指定锁的超时时间,则使用默认 30 s 【lockWatchDogTimeout】。只要加锁成功,就会启动定时任务,重新给 锁设置过期时间(30 s),定时任务的时间默认是 看门狗的 1/3

redis实现可重入锁

使用线程的ThreadLocal变量存储当前线程持有锁的计数。线程获取锁后将Redis中的value保存在ThreadLocal中,同一线程再次尝试获取锁的时候就先将 ThreadLocal 中的 值 与 Redis 的 value 比较,如果相同则表示这把锁所以该线程,即实现可重入锁。

或 redisson 的RLock

可重入读写锁RReadWriteLockJava对象实现了java.util.concurrent.locks.ReadWriteLock接口。分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。

数据库

zookeeper