测试方法:
public void testRedisLock() throws InterruptedException {
boolean flag = true;
int count = 0;
while (flag) {
count++;
System.out.println("线程一:查询第"+count+"次!");
//加锁
long time = System.currentTimeMillis() + TIMEOUT;
flag = redisLock.lock("finance:***Service-methodName", String.valueOf(time));
if (flag) {
//加锁成功
//锁定操作代码
System.out.println("我被锁了 谁也别想再访问我!");
//业务代码...
Thread.sleep(1000*8);
//解锁 并跳出循环
redisLock.unlock("finance:***Service-methodName", String.valueOf(time));
flag = false;
System.out.println("操作完成 锁已被我释放!");
} else {
//加锁失败
//继续加锁操作
flag = true;
}
}
}
RedisLock:
public class RedisLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 加锁
*
* @param key productId - 唯一标志
* @param value 当前时间+超时时间 也就是时间戳
* @return
*/
public boolean lock(String key, String value) {
//对应setnx命令
if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
//可以成功设置,也就是key不存在
return true;
}
//判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁
String currentValue = stringRedisTemplate.opsForValue().get(key);
//如果锁过期
//currentValue不为空且小于当前时间
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间value
//对应getset,如果key存在
String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
//假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。
//而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
//oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发
return true;
}
}
return false;
}
/**
* 解锁
*
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = stringRedisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
//删除key
stringRedisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("[Redis分布式锁] 解锁出现异常了,{}", e);
}
}
}