在高并发多线程的环境下,一些数据的存取操作会遇到许多的问题,导致数据污染
一般的情况下我们会给我们操作数据的代码上锁,就是加个 synchronized ,这样这块代码就是同步的了,每次只能有一个线程进入
那么数据自然不会被污染了
但是这样的操作也是存在问题的 1.由于变成单线程,这一块业务的操作变得非常缓慢,请求多的时候等待时间特别长
2.synchronized 只能在单服务器上使用,现在我们往往追求的是分布式的系统,synchronized 就用不了了
所以今天介绍一个加锁方法:使用 Redis 实现分布式锁
先介绍一下 Redis ,Redis 其实是一个 Key-Value 类型的数据库
它有两个操作
1. SETNX ,这是一个 set 操作,但是它会进行一个判断, SETNX( key , value ) 的时候它会先查看以下这个 key 是否有值
如果无值,那么吧 value 设置进去,并返回 true
如果有值,放弃插入操作,并返回 false
2. GETSET ,这也是一个 set 操作,但是它会先取出原本这个 key 对应 value 值,也就是回返回旧的值
然后还有一点很关键的是, Redis 数据库可以设置生命时长,超过了时间数据就会失效
但是我们并不用它自带的这个来判断超时
做法:
1.首先我们定义一个 key 的名称作为我们这个锁对象
2.我们设置的 value = 当前系统时间 + 超时时间
private static final long TIMEOUT = 10 * 1000; // 超时时间10s
long time = System.currentTimeMillis() + TIMEOUT;
String value = String.valueOf(time);
3.先使用 SETNX 方法(java redis 里这个方法叫做 setIfAbsent() )把 key 和 value 设置进去
如果返回 true 则表示设置成功,之前没有人设置过
如果返回 false 则表示这个锁已经有人使用了,但是我们要判断它是否超时了,如果是已经超时了的话我们还是可以使用的
先取出这个 key 里的旧值,与当前时间比较,看当前时间是否已经超过了设置的值,如果超过了,那么就表示已经超时,我们可以设置新的值,我们使用 GETSET 方法设置新的值,把返回的旧值与我们之前取出来的旧值比较,如果一致的话表示我们成功更新了旧的值,获得了锁,如果不一致的话那么表示这个锁在这个期间又被别人获取走了,那么我们获取失败
4.使用完毕之后解锁:
取出 key 中的 value 值,与我们原先设置进去的进行比较,如果一致,表示这还是我们持有的锁,我们可以把这个 key delete掉了,但是如果取出的值与我们原本的值不一致,那么说明这个锁现在已经被别人获取了,那么我们就不能去删除别人的值了,直接退出操作就行了。
下面是封装好的 Redis 锁工具类
/**
* @author: 林之谦
* @date: 2018/8/5
* @description:
*/
@Component
@Slf4j
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key
* @param value 当前时间 + 超时时间
* @return
*/
public boolean lock(String key,String value){
//如果可以成功设置,可以加锁
if(redisTemplate.opsForValue().setIfAbsent(key,value)){
return true;
}
String currentVal = redisTemplate.opsForValue().get(key);
// 如果锁过期
if(!StringUtils.isEmpty(currentVal)
&& Long.parseLong(currentVal) < System.currentTimeMillis()){
// 获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key,value);
// 更新锁时间成功,则成功加锁
if(!StringUtils.isEmpty(oldValue) && oldValue.equals(currentVal)){
return true;
}
}
return false;
}
public void unlock(String key,String value){
try {
String currentVal = redisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(currentVal) && currentVal.equals(value)){
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e){
log.error("【redis 分布式锁】 解锁异常, {}",e);
}
}
}