redis乐观锁和悲观锁在spring boot的使用
Posted lllllLiangjia
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis乐观锁和悲观锁在spring boot的使用相关的知识,希望对你有一定的参考价值。
目录
乐观锁:
- 认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
- 获取version
- 更新时候比较version
使用监听操作和事务操作实现乐观锁
测试多线程修改值,使用watch可以当作redis的乐观锁操作
在事务执行之前先watch key,当事务前后key改变了事务失败,那就先取消监视,再重新监视进行事务操作
使用乐观锁
@RestController
public class DemoController
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@GetMapping("/demo")
public void optimisticLock()
Thread thread = new Thread(new Task(redisTemplate));
thread.start();
任务类
代码实现对redis中number的值进行自增,通过while死循环,当事务提交成功时,则跳出循环结束,任务失败的话那就一直尝试,跟cas操作一样。
注:在实际使用中,如果为了避免死锁的发生。可以将while死循环换成for循环,在尝试一定的次数还未成功的话跳出循环,返回一个错误信息。
public class Task implements Runnable
private RedisTemplate<String,Object> redisTemplate;
//将IoC容器中的redisTemplate对象传入进来进行调用
public Task(RedisTemplate<String,Object> redisTemplate)
this.redisTemplate = redisTemplate;
@Override
public void run()
//让一个连接直接执行多条redis操作语句的方法就是使用SessionCallback
redisTemplate.execute(new SessionCallback()
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException
List<Object> exec = null;
while (true)
//监控key为number的数据
redisOperations.watch("number");
int number = (int)redisOperations.opsForValue().get("number");
System.out.println(number);
// 开启事务
redisOperations.multi();
// value自增
redisOperations.opsForValue().increment("number");
try
// 提交事务
exec = redisOperations.exec();
// 如果提交成功返回的list长度不为0,退出循环
if (exec.size() != 0)
break;
System.out.println("被修改了,重新加");
catch (Exception e)
System.out.println("报错了");
return null;
);
发送了500个http请求,最终乐观锁成功起效。最终的执行结果截图如下
悲观锁
什么时候都会出问题,无论做什么都会加锁
悲观锁分为两种一种是使用setnx,另一种是分布式锁redission
使用setnx
redis命令中两个操作关键字setex和setnx对悲观锁实现起到了作用。
setex key 时间 value(set with expire):设置过期时间
setnx(set if not exist):不存在就添加
@Autowired
private RedisTemplate<String,Object> redisTemplate;
public String setNx() throws InterruptedException
//进行加锁操作,对应redis命令setnx(set if not exist):不存在就添加
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("1", "1");
if (aBoolean)
// 加锁成功走如下逻辑
// 对应redis命令setex key 时间 value(set with expire):设置过期时间,防止死锁
redisTemplate.expire("1",1, TimeUnit.MINUTES);
/**
* 处理业务逻辑
*/
// 解除锁
redisTemplate.delete("1");
System.out.println(Thread.currentThread().getName()+"。。。。。。返回缓存数据");
return null;
else
// 加锁失败
// 再次调用该方法,进行获取锁
setNx();
return null;
分布式锁redission
引入pom
<!--Redisson分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
配置类
@Configuration
public class RedissonConfig
// 配置文件中你自己的redis地址
@Value("$spring.redis.host")
private String host;
// 配置文件中你自己的redis端口号
@Value("$spring.redis.port")
private String port;
// 配置文件中你自己的redis密码
@Value("$spring.redis.password")
private String password;
@Bean
public RedissonClient getRedisson()
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port)
.setPassword(password);
//添加主从配置
return Redisson.create(config);
@RestController
public class DemoController
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Resource
private RedissonClient redissonClient;
/**
* 使用redis悲观锁
*/
@GetMapping("/demo")
public void optimisticLock()
Thread thread = new Thread(new PessimisticLockTask(redisTemplate,redissonClient));
thread.start();
任务类
public class PessimisticLockTask implements Runnable
private RedisTemplate<String,Object> redisTemplate;
private RedissonClient redissonClient;
public PessimisticLockTask(RedisTemplate<String,Object> redisTemplate,RedissonClient redissonClient)
this.redisTemplate = redisTemplate;
this.redissonClient = redissonClient;
@Override
public void run()
//加锁(redis分布式锁)
RLock lock = redissonClient.getLock("lock");
//设置锁超时时间,防止异常造成死锁
lock.lock(2, TimeUnit.SECONDS);
try
redisTemplate.opsForValue().increment("number");
System.out.println(redisTemplate.opsForValue().get("number"));
catch (Exception e)
System.out.println("发生异常");
System.out.println(e);
finally
lock.unlock();
总结
- 三种加锁方式都能保证并发情况下数据的安全一致性,但各有千秋。
- 相对来说使用redission会更加方便,直接一个加锁和解锁操作就可以实现,让开发人员更注重自己的逻辑代码就好。
- 而使用watch、事务相结合的乐观锁和setnx的悲观锁,由于都是直接调用的redis中的指令操作,我们要自己在代码中写加锁和解锁逻辑,虽然繁琐点,但是要更加灵活可配,开发人员可以控制任务失败循环的次数等操作,可以在此基础上开发一些新的功能点。
以上是关于redis乐观锁和悲观锁在spring boot的使用的主要内容,如果未能解决你的问题,请参考以下文章