Java+Redis:使用java实现分布式锁--redis篇
使用java实现分布式锁-redis篇
·
当前网上可以找到许多基于redis使用java实现的分布式锁的代码,其主要实现方式主要有以下几种:
1. SETNX、GETSET、GET、DEL
加锁时,使用SETNX设置锁名和锁的到期时间,若设置成功则获取锁;否则再检查锁是否已过期,是则使用GETSET设置新的到期时间,设置成功则获取到锁,获取到锁后记一下状态;解锁时,若锁已过期则直接解锁,否则根据状态判断是否由自己执有锁,是则解锁。
2. SETNX、EXPIRE、GET、DEL
加锁时,使用SETNX设置锁名和锁的执有者,使用EXPIRE设置锁的过期时间;解锁时,先查询锁是否还由锁执有者执有,是则直接DEL解锁。
这两种实现方式在实际代码中都会各种各样的问题,上代码来分析:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
public class WrongARedisDLock implements DLock {
private static final Logger LOG = LogManager.getLogger(WrongARedisDLock.class);
private final String lockName;
private final int lockDuration;
private final String lockSubject;
private final Jedis jedis;
public WrongARedisDLock(String lockName, int lockDuration, String lockSubject, Jedis jedis) {
this.lockName = lockName;
this.lockDuration = lockDuration;
this.lockSubject = lockSubject;
this.jedis = jedis;
}
public void lock() {
tryLock(Long.MAX_VALUE);
}
public boolean tryLock() {
return tryLock(0);
}
public boolean tryLock(long waitTimeout) {
long startTime = System.currentTimeMillis();
while (true) {
try {
// 设置锁名和锁申请主体的唯一标识
if (jedis.setnx(lockName, lockSubject) != null) {
// 若此时程序故障退出,锁将无法释放
jedis.expire(lockName, lockDuration);
return true;
}
String lockOwner = jedis.get(lockName);
if (lockSubject.equals(lockOwner)) {
return true;
}
if (lockOwner == null) {
continue;
}
if (System.currentTimeMillis() - startTime < waitTimeout) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
continue;
}
return false;
} catch (Exception e) {
LOG.error("tryLock error", e);
if (System.currentTimeMillis() - startTime < waitTimeout) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
continue;
}
return false;
}
}
}
public void unlock() {
try {
String lockOwner = jedis.get(lockName);
if (lockSubject.equals(lockOwner)) {
// 若此时锁过期了并且被其它申请者成功获取,则将误删锁
jedis.del(lockName);
}
} catch (Exception e) {
LOG.error("unlock error", e);
}
}
}
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
public class WrongBRedisDLock implements DLock {
private static final Logger LOG = LogManager.getLogger(WrongBRedisDLock.class);
private final String lockName;
private final long lockDuration;
private final String lockSubject;
private final Jedis jedis;
private boolean isLocked = false;
public WrongBRedisDLock(String lockName, long lockDuration, String lockSubject, Jedis jedis) {
this.lockName = lockName;
this.lockDuration = lockDuration;
this.lockSubject = lockSubject;
this.jedis = jedis;
}
public void lock() {
tryLock(Long.MAX_VALUE);
}
public boolean tryLock() {
return tryLock(0);
}
public boolean tryLock(long waitTimeout) {
long loopStartTime = System.currentTimeMillis();
while (true) {
try {
long startTime = System.currentTimeMillis();
String expireTimeStr = String.valueOf(startTime + lockDuration + 1);
// 设置锁名和锁的到期时间,此处就存在多个不同进程或服务器当前时间可能会不一致的问题
if (jedis.setnx(lockName, expireTimeStr) != null) {
isLocked = true;
return true;
}
// 获取锁的过期时间
String oldExpireTimeStr = jedis.get(lockName);
if (oldExpireTimeStr == null) {
continue;
}
long oldExpireTime = Long.parseLong(oldExpireTimeStr);
if (startTime > oldExpireTime) {
// 在并发量较大时,可能会有多个锁申请主体同时进入到这里,并且都会修改锁的过期时间,
// 这样会造成锁的实际过期时间比锁执有者设置的时间靠后
oldExpireTimeStr = jedis.getSet(lockName, expireTimeStr);
if (oldExpireTimeStr != null) {
// 以下代码可以保证第一个申请成功的主体获取到锁
oldExpireTime = Long.parseLong(oldExpireTimeStr);
if (startTime > oldExpireTime) {
isLocked = true;
return true;
}
}
}
if (System.currentTimeMillis() - loopStartTime < waitTimeout) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
continue;
}
return false;
} catch (Exception e) {
LOG.error("tryLock error", e);
if (System.currentTimeMillis() - loopStartTime < waitTimeout) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
continue;
}
return false;
}
}
}
public void unlock() {
try {
// 获取锁的到期时间
String oldExpireTimeStr = jedis.get(lockName);
if (oldExpireTimeStr != null) {
long oldExpireTime = Long.parseLong(oldExpireTimeStr);
if (oldExpireTime < System.currentTimeMillis()) {
// 若此时锁过期了并且被其它申请者成功获取,则将误删锁
jedis.del(lockName);
} else {
if (isLocked) {
// 若此时锁过期了并且被其它申请者成功获取,则将误删锁
jedis.del(lockName);
}
}
}
isLocked = false;
} catch (Exception e) {
LOG.error("unlock error", e);
}
}
}
正确的实现方式如下:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class RedisDLock implements DLock {
private static final Logger LOG = LogManager.getLogger(RedisDLock.class);
private final String lockName;
private final long lockDuration;
private final String lockSubject;
private final Jedis jedis;
public RedisDLock(String lockName, long lockDuration, String lockSubject, Jedis jedis) {
this.lockName = lockName;
this.lockDuration = lockDuration;
this.lockSubject = lockSubject;
this.jedis = jedis;
}
public void lock() {
tryLock(Long.MAX_VALUE);
}
public boolean tryLock() {
return tryLock(0);
}
public boolean tryLock(long waitTimeout) {
long startTime = System.currentTimeMillis();
while (true) {
try {
// 尝试加锁。等同于 SETNX + EXPIRE,通过设置一个属性值完成加锁过程
if (jedis.set(lockName, // 锁名
lockSubject, // 锁申请人
"NX", // 锁名不存在时插入
"PX", // 锁的有效时长的时间单位为millisecond
lockDuration // 锁的有效时长
) != null) {
// 加锁成功
return true;
}
// 若加锁不成功,获取锁的拥有者。加锁不成功有可能是申请者已执有此锁,也可能是其他人执有此锁
String lockOwner = jedis.get(lockName);
// 若锁执有者与锁申请者相同,则返回申请者已占用此锁
if (lockSubject.equals(lockOwner)) {
return true;
}
// 若锁未被占用,则再次申请锁
if (lockOwner == null) {
continue;
}
if (System.currentTimeMillis() - startTime < waitTimeout) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
continue;
}
// 返回锁已被其他申请主体占用
return false;
} catch (Exception e) {
LOG.error("tryLock error", e);
if (System.currentTimeMillis() - startTime < waitTimeout) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
continue;
}
return false;
}
}
}
// 使用transaction解锁
public void unlock1() {
try {
jedis.watch(lockName); // 添加对锁的监控,若锁的相关属性发生变化,则整个事务将不执行
String lockOwner = jedis.get(lockName);
if (lockSubject.equals(lockOwner)) {
// 若锁未超时,则del操作会删除锁记录
// 若锁已超时,当前锁无执有者,则del操作不会造成影响
// 若锁已超时,当前锁被其他人执有,由于watch监控到锁的属性有变化,则事务中的del操作不会真正执行
Transaction tx = jedis.multi();
tx.del(lockName);
tx.exec();
} else {
jedis.unwatch(); // 若不再执有锁,则放开监控
}
} catch (Exception e) {
LOG.error("unlock error", e);
}
}
// 使用lua script解锁
public void unlock() {
try {
String script =
"if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
jedis.eval(script,Collections.singletonList(lockName),Collections.singletonList(lockSubject));
} catch (Exception e) {
LOG.error("unlock error", e);
}
}
}
点击阅读全文
更多推荐
活动日历
查看更多
直播时间 2025-02-26 16:00:00


直播时间 2025-01-08 16:30:00


直播时间 2024-12-11 16:30:00


直播时间 2024-11-27 16:30:00


直播时间 2024-11-21 16:30:00


所有评论(0)