一、 测试
@Autowired
private RedisLockHelper redisLockHelper;
private static final String LOCK ="lock:equipment";
// 14: 48执行
//@Scheduled(cron = "0 48 14 ? * *")
public void orderSync() {
long time = System.currentTimeMillis() + (10*1000);
//进行加锁操作
if (redisLockHelper.lock(LOCK, time)) {
System.out.println("加锁");
}else {
System.out.println("解锁");
redisLockHelper.unlock(LOCK,time);
}
}
二、操作redis的工具类
package com.kmnfsw.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
*
* @author wyg
*/
@Component
@Slf4j
public class RedisLockHelper {
@Autowired
private RedisTemplate redisTemplate;
/**
* 加锁
* @param target 唯一标志
* @param timeStamp 当前时间+超时时间 也就是时间戳
* @return
*/
public boolean lock(String target, long timeStamp){
// 如果键不存在则新增,存在则不改变已经有的值。
if(redisTemplate.opsForValue().setIfAbsent(target,timeStamp)){
return true;
}
// 判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁
long currentLock = (long) redisTemplate.opsForValue().get(target);
// 如果锁过期 currentLock不为空且小于当前时间
if(currentLock < System.currentTimeMillis()){
// 获取上一个锁的时间value 对应getset,如果lock存在 设置给过来的值,并返回旧的值
long preLock = (long) redisTemplate.opsForValue().getAndSet(target,timeStamp);
// 假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentLock=A(get取的旧的值肯定是一样的),两个线程的timeStamp都是B,key都是K.锁时间已经过期了。
// 而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的timeStamp已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
if(preLock==currentLock){
// preLock不为空且preLock等于currentLock,也就是校验是不是上个对应的商品时间戳,也是防止并发
return true;
}
}
return false;
}
/**
* 解锁
* @param target
* @param timeStamp
*/
public void unlock(String target,long timeStamp){
try {
long currentValue = (long) redisTemplate.opsForValue().get(target);
if( currentValue<=timeStamp){
// 删除锁状态
redisTemplate.opsForValue().getOperations().delete(target);
}
} catch (Exception e) {
log.error("警报!警报!警报!解锁异常{}",e);
}
}
}
还有一种写法:
public boolean lock(String target) {
String uuid = UUID.randomUUID().toString();
// 如果键不存在则新增,存在则不改变已经有的值。设置超时时间(解决异常,解决绝宕机)
if (redisTemplate.opsForValue().setIfAbsent(target, uuid, 10, TimeUnit.SECONDS)) {
return true;
}
try {
long Lock = (long) redisTemplate.opsForValue().get(target);
//业务代码
}catch (Exception e){
e.printStackTrace();
}finally {
//解决自己的锁自己释放
if(uuid.equals(redisTemplate.opsForValue().get(target))){
redisTemplate.delete(target);
}
}
//注:这里有个缺点,当执行代码的时间超过锁的过期时间,会导致锁被释放掉,或者导致永久失效,这是需要加线程延长锁的时间或者用redission框架
}
还有一种redission加锁
@Autowired
private RedissonClient redissonClient
/*
* 这里只演示可重入锁,其他锁详情请查看官方文档
*/
// 获取redisson锁对象
RLock lock = redissonClient.getLock("anyLock");
//(1) 最常见的使用方法
lock.lock();
//(2) 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
// (3)Redisson同时还为分布式锁提供了异步执行的相关方法
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.3</version>
</dependency>