使用java基于Redis实现一个简单的可重入分布式锁
- 由于Redis是单线程的,并且
SETNX
(即:如果不存在就创建命令)命令可实现的原子性操作,从而可用来实现 分布式锁 - 这里使用
Jedis
来作为客户端工具来调用Redis
功能 - 但是仍有一个问题,由于使用
jedis.setnx()
和jedis.expire()
为非原子性操作,可能会有问题,即如果系统在调用jedis.setnx()
后崩溃,未及时调用jedis.expire()
从而无法设置自动释放锁超时时间,可能会造成死锁。(这里主要是思路,先不考虑这个问题,如果要解决这个问题,可能需要使用脚本直接操作Redis
) - 以下是代码:
package top.petwanglei.common.component.lock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import top.petwanglei.common.pojo.base.MyProException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyRedisDistributedLock implements Lock {
// 锁名(必须唯一)
private String lockName;
// 当前本地IP
private String localHost;
// 当前本地工作进程编号
private String localWorkerNum;
// 自动释放锁超时时间(默认为0秒,表示无超时自动释放锁机制,单位:秒)(建议设置,防止死锁)
private int timeout = 0;
// redis连接池
private JedisPool jedisPool;
// 锁重入计数
private ThreadLocal<Integer> reentrantCount = new ThreadLocal<>();
public MyRedisDistributedLock(String lockName, String localHost, String localWorkerNum, int timeout, JedisPool jedisPool) {
if (lockName == null || lockName.trim().equals("")) {
throw new MyProException("未指定锁名");
} else if (jedisPool == null || jedisPool.isClosed()) {
throw new MyProException("Redis未初始化");
} else if (timeout < 0) {
throw new MyProException("超时时间指定错误");
}
String ip = "unknowed";
if (localHost == null || localHost.trim().equals("")) {
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {}
} else {
ip = localHost;
}
this.lockName = lockName;
this.localHost = ip;
this.localWorkerNum = (localWorkerNum == null || localWorkerNum.trim().equals("")) ? "0" : localWorkerNum;
this.timeout = timeout;
this.jedisPool = jedisPool;
this.reentrantCount.set(0);
}
/**
* 尝试获取锁(立马返回)
* @return
*/
@Override
public boolean tryLock() {
boolean result = false;
// 获取当前锁的重入次数
int count = this.reentrantCount.get();
// 判断是否已经获取锁
if (count > 0) {
// 增加一次上锁次数(重入)
this.reentrantCount.set(++count);
// 上锁成功
result = true;
} else {
try (Jedis jedis = this.jedisPool.getResource()) {
// redis实现原子性操作
Long num = jedis.setnx(lockName, localHost + "-" + localWorkerNum + "-" + System.currentTimeMillis());
if (num > 0L) {
if (this.timeout > 0l) {
// 设置锁的超时释放时间
jedis.expire(lockName, timeout);
}
// 增加上锁次数
this.reentrantCount.set(1);
// 上锁成功
result = true;
}
}
}
return result;
}
/**
* 上锁(未上锁成功则自旋等待)
*/
@Override
public void lock() {
int i = 0;
// 自旋锁
while (!tryLock()) {
if (i >= 3) {
// 让出当前时间片
Thread.yield();
} else {
i++;
}
}
}
/**
* 解锁
*/
@Override
public void unlock() {
// 获取当前锁的重入次数
int count = this.reentrantCount.get();
if (count > 1) {
this.reentrantCount.set(--count);
} else if (count == 1) {
try (Jedis jedis = this.jedisPool.getResource()) {
// 彻底释放锁
jedis.del(lockName);
this.reentrantCount.set(0);
}
}
}
/************************************以下功能暂未实现************************************/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void lockInterruptibly() throws InterruptedException {}
@Override
public Condition newCondition() {
return null;
}
}