一. 概述
在java开发中, 多线程环境下为了保证一个代码块在同一时间只能由一个线程访问, 我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式。但是现在公司都是流行分布式架构,在分布式环境下,如何保证不同节点的线程同步执行呢?今天我们来介绍利用Redis实现分布式锁
二. Redis分布式锁简单实用
2.1 导入Redisson依赖包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.2</version>
</dependency>
2.2 使用
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000) // cluster state scan interval in milliseconds
.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002");
// 配置redis地址
RedissonClient redisson = Redisson.create(config);
// 获取实例
RLock lock = redisson.getLock("anyLock");
// 获取锁
lock.lock();
try {
// 执行业务代码块
...
} finally {
// 释放锁
lock.unlock();
}
三. 自己实现一个简单的redis锁
public class RedisLockUtil {
private Logger log = LoggerFactory.getLogger(RedisDao.class);
@Resource
private StringRedisTemplate stringRedisTemplate;
// 当前线程唯一常量
private ThreadLocal<String> threadKeyId = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
/**
* redis获取锁,获取不到就会不断重试
* @param key 锁名
* @param time 锁的最大有效时间(秒)
*/
public void lock(String key,int time){
// 锁名
String keyName = getLockName(key);
while(true){
// 争抢锁
Boolean flag = setnxAndExpire(keyName, threadKeyId.get(), time);
// 判断key是否存在
if(!flag){
// 判断是否重入锁
if(threadKeyId.get().equals(String.valueOf(stringRedisTemplate.opsForValue().get(keyName)))){
break;
}
// 短暂休眠,nano避免出现活锁
try {
Thread.sleep(500, 500);
} catch (InterruptedException e) {
break;
}
}else {
break;
}
}
}
/**
* 尝试获取锁,立即返回结果
* @param key
* @param time
* @return
*/
public Boolean tryLock(String key,int time){
// 锁名
String keyName = getLockName(key);
return setnxAndExpire(keyName, threadKeyId.get(), time);
}
/**
* 释放锁
* @param key
*/
public void unlock(String key){
// 锁名
String keyName = getLockName(key);
// 释放锁
if (threadKeyId.get().equals(stringRedisTemplate.opsForValue().get(keyName))) {
stringRedisTemplate.delete(keyName);
}
}
/**
* 获取锁的名字
* @param key
* @return
*/
public String getLockName(String key){
if(null != key){
return "REDIS_LOCK_"+key;
}else {
return "DEFAULT_LOCK";
}
}
/**
* key不存在时设置value,一般用于争抢锁
* 把setnx和expire合并,保证原子性,
* @param key
* @param value 值
* @param time 秒
* @return
*/
public Boolean setnxAndExpire(String key ,String value,long time){
/**
* set key value [EX seconds] [PX milliseconds] [NX|XX]
* EX seconds:设置失效时长,单位秒
* PX milliseconds:设置失效时长,单位毫秒
* NX:key不存在时设置value,成功返回OK,失败返回(nil)
* XX:key存在时设置value,成功返回OK,失败返回(nil)
*
* 示例: set name p7+ ex 100 nx
* SET操作成功后,返回的是OK,失败返回NIL
*/
return stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
/**
* 给定命令与给定参数一起的“本机”或“原始”执行。 该命令按原样执行,并且尽可能少地执行“解释”,这取决于调用方对参数或结果的任何处理
*/
Object obj = connection.execute("set", SafeEncoder.encode(key), SafeEncoder.encode(value), SafeEncoder.encode("EX"), Protocol.toByteArray(time), SafeEncoder.encode("NX"));
return obj != null;
});
}
}