redis乐观锁和悲观锁在spring boot的使用

Posted lllllLiangjia

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis乐观锁和悲观锁在spring boot的使用相关的知识,希望对你有一定的参考价值。

目录

乐观锁:

使用乐观锁

任务类

悲观锁

使用setnx

分布式锁redission

引入pom

配置类

任务类

总结


乐观锁:

  • 认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取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的使用的主要内容,如果未能解决你的问题,请参考以下文章

redis乐观锁和悲观锁在spring boot的使用

乐观锁和悲观锁

乐观锁和悲观锁

悲观锁和乐观锁,啥情况

悲观锁和乐观锁

面试官:你了解乐观锁和悲观锁吗?