public class JedisClient {
private static final String isOK = "OK";
/**
* 正确的获取分布式锁
*
* @param jedis jedis对象
* @param lockKey 使用key当锁
* @param requestId 客户端请求唯一标识
* @param expire 过期时间
*/
public static boolean tryDistributedLock(Jedis jedis, String lockKey, String requestId, int expire) {
// NX, 可以保证只有一个客户端持有锁, 满足互斥性
// 1. 第三个参数为nxxx, NX 表示 SET IF NOT EXIST 即当key不存在时, 进行set操作, 否则不做任何操作
// 过期时间, 可以保证不会发生死锁
// 2. 第四个参数pxxx, 给这个key加一个过期时间
// 3. 第五个参数expx, 配合第四个参数使用, 代表key的过期时间
String result = jedis.set(lockKey, requestId, "NX", "PX", expire);
if (isOK.equals(result)) {
return true;
}
return false;
}
/**
* 错误的获取分布式锁一
*
* @param jedis jedis对象
* @param lockKey 使用key当锁
* @param requestId 客户端请求唯一标识
* @param expire 过期时间
*/
public static boolean wrongDistributedLock(Jedis jedis, String lockKey, String requestId, int expire) {
if (jedis.setnx(lockKey, requestId) == 1) {
// 由于这里执行了两条redis的命令, 不具备原子性
// 如果程序崩溃, 导致没有执行设置过期时间, 那么有可能会发生死锁
jedis.expire(lockKey, expire);
return true;
}
return false;
}
/**
* 错误的获取分布式锁二
*
* @param jedis jedis对象
* @param lockKey 使用key当锁
* @param expire 过期时间
*/
public static boolean wrongDistributedLock2(Jedis jedis, String lockKey, int expire) {
long expireTime = System.currentTimeMillis() + expire;
// 如果当前锁不存在, 返回加锁成功
if (jedis.setnx(lockKey, String.valueOf(expireTime)) == 1) {
return true;
}
// 如果锁存在, 获取锁的过期时间
String currentExpireTime = jedis.get(lockKey);
if (null != currentExpireTime && Long.parseLong(currentExpireTime) < System.currentTimeMillis()) {
// 锁已过期, 获取上一个锁的过期时间, 并设置现在的过期时间
String oldExpireTime = jedis.getSet(lockKey, String.valueOf(expireTime));
if (null != oldExpireTime && oldExpireTime.equals(currentExpireTime)) {
// 考虑多线程并发时, 只有一个线程的设置值和当前值相同, 它才有权加锁
// 有点cas的感觉..只有期望值相等时, 才允许赋值..但是可能会出现类似ABA的问题..
return true;
}
}
return false;
// 问题一 由于客户端生成过期时间, 所以需要强制要求分布式下每个客户端的时间必须同步
// 问题二 如果多个客户端同时执行getSet方法, 虽然最终只有一个客户端可以加锁成功, 但是这个客户端锁的过期时间可能被其他客户端覆盖
// 问题三 锁不具备客户端标识, 任何客户端都可以解锁
}
/**
* 正确的释放分布式锁
*
* @param jedis jedis对象
* @param lockKey 使用key当锁
* @param requestId 客户端请求唯一标识
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
// Lua脚本代码
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// KEYS[1]赋值为lockKey
// ARGV[1]赋值为requestId
// redis在eval命令执行Lua代码时, 将被当作一个命令去执行, 确保了原子性
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (isOK.equals(result)) {
return true;
}
return false;
}
/**
* 错误的释放分布式锁一
*/
public static boolean wrongReleaseDistributedLock1(Jedis jedis, String lockKey) {
// 任意客户端都可以解锁
return jedis.del(lockKey) == 1;
}
/**
* 错误的释放分布式锁二
*/
public static boolean wrongReleaseDistributedLock2(Jedis jedis, String lockKey, String requestId) {
// 判断加锁与解锁是不是同一个客户端
if (requestId.equals(jedis.get(lockKey))) {
// 由于不具备原子性,如果此时这把锁突然不是这个客户端的,则会误解锁
return jedis.del(lockKey) == 1;
}
return false;
}
}