热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

再谈使用java实现分布式锁redis篇

当前网上可以找到许多基于redis使用java实现的分布式锁的代码,其主要实现方式主要有以下几种:1.SETNX、GETSET、GET、DEL加锁时

当前网上可以找到许多基于redis使用java实现的分布式锁的代码,其主要实现方式主要有以下几种:

1.      SETNXGETSET、GET、DEL

加锁时,使用SETNX设置锁名和锁的到期时间,若设置成功则获取锁;否则再检查锁是否已过期,是则使用GETSET设置新的到期时间,设置成功则获取到锁,获取到锁后记一下状态;解锁时,若锁已过期则直接解锁,否则根据状态判断是否由自己执有锁,是则解锁。

2.      SETNXEXPIRE、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 }

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 }

正确的实现方式如下:

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", // 锁的有效时长的时间单位为millisecondlockDuration // 锁的有效时长) != null) {// 加锁成功return true;}// 若加锁不成功,获取锁的拥有者。加锁不成功有可能是申请者已执有此锁,也可能是其他人执有此锁String lockOwner = jedis.get(lockName);// 若锁执有者与锁申请者相同,则返回申请者已占用此锁if (lockSubject.equals(lockOwner)) {return true;}// 若锁未被占用,则再次申请锁if (lockOwner == null) {continue;}if (System.currentTimeMillis() - startTime }












推荐阅读
author-avatar
小老特
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有