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

本文详细介绍了如何在 Redis 中实现乐观锁和悲观锁。乐观锁通过监听和事务操作确保数据一致性,避免了死锁,而悲观锁则通过 setnx 和分布式锁 Redission 实现。在多线程环境下,乐观锁通过不断重试直至事务成功,而悲观锁则在操作前加锁,确保独占资源。Redission 提供了简单易用的分布式锁,但在需要更多控制时,直接使用 Redis 指令如 watch 和 setnx 更具灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

乐观锁:

使用乐观锁

任务类

悲观锁

使用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中的指令操作,我们要自己在代码中写加锁和解锁逻辑,虽然繁琐点,但是要更加灵活可配,开发人员可以控制任务失败循环的次数等操作,可以在此基础上开发一些新的功能点。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lllllLiangjia

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值