在学习Redis的分布式锁时,遇到一些问题记录下来(基于spring boot)
需要加锁的类
@Service
public class TestLockImpl implements TestLock {
//总票数
public static int total = 1000000;
public static int all = 1000000;
int count = 0;
@Override
public synchronized String getTicket() {
total--;
try{
//如果看不出效果,可以加入延迟
//Thread.sleep(200);
count++;
}catch (Exception e){}
// redisLock.unlock(total+"",time+"");
return "总票数为="+all+"---------"+"剩余票数为"+total+"购买人数为"+count;
}
如果使用synchronized 关键字,的确可以解决并发问题,但是效率太慢,用压测工具进行1000个并发测试,发起10000条请求,需要的时间如下图
下面介绍使用redis分布式锁,主要介绍看代码注解
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 加锁
*
* @param key 票数,即需要加锁的类的total
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
//相对于redis中的setNX
if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//如果上面代码加锁失败,根据key去redis获取value值
String currentValue = stringRedisTemplate.opsForValue().get(key);
//如果锁过期,这是为了解决redis死锁的问题,即你用redis进行了锁,但是代码还没运行到解锁,
//程序出现异常,就不会运行到解锁的代码了
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {//currentValue不为空且小于当前时间
//锁过期,拿到原来的值,为key设置新的value,即新的过期时间
//注意,这里是先拿旧的value,再设置新的value
String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
//这里是为了解决并发问题,假设锁已经过期,两个线程同时访问到这里时,线程一访问,
//currentValue 和oldValue此时都一样,都为A,当线程B执行时,currentValue 依然为A,但是oldValue
//已经被改成了B,所以线程二无法return true
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key,String value){
String currentValue = stringRedisTemplate.opsForValue().get(key);
try {
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
stringRedisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e){
System.out.println("出异常了");
}
}
}
当使用redis分布式锁时,应该这样给service加锁
@Service
public class TestLockImpl implements TestLock {
//总票数
public static int total = 1000000;
public static int all = 1000000;
int count = 0;
@Autowired
RedisLock redisLock;
@Override
public String getTicket() {
//超时时间为10秒
long time = System.currentTimeMillis()+10*1000;
//加锁
if(!redisLock.lock(total+"",time+"")){
throw new RuntimeException("出错了");
}
total--;
try{
//Thread.sleep(200);
count++;
//Thread.sleep(200);
}catch (Exception e){}
//解锁
redisLock.unlock(total+"",time+"");
return "总票数为="+all+"---------"+"剩余票数为"+total+"购买人数为"+count;
}
}
再次使用压测,速度的提升还是挺多的: