redis 分布式锁1 redisson

11 篇文章 0 订阅
7 篇文章 0 订阅

各场景下线程安全的锁

一、同步锁:当在一个java虚拟机多个线程操作一个变量的时候就会出现线程安全问题,这个时候就会用到同步锁。

二、异步锁:就是多个java 虚拟机或者说是服务器,操作同一个变量是,会出现线程安全问题,使用需要使用异步锁来处理。

1)数据库  乐观锁 悲观锁 唯一标示  不推荐使用,容易出现锁表,出现死锁。

2)Redis 分布式锁: 就是设置一个flag标识,当一个服务拿到锁以后立即把对应的标识设置为false  用完后释放锁,并把标识修改为true。

3)使用dubbo  zookeeper (共享锁,排它锁),这里就根据自己的情况,共享锁还是会出现阻塞的情况,排它锁就是会生成很多临时的节点,谁先获取最小的序号标识谁就先获取到锁。

 

分布式锁场景

【redis 是单线程的】

互联网秒杀

抢优惠券

 

如果此时 用 synchronize 这种JVM级别的锁,只会锁住当前 JVM里的那部分

如果有多个 tomcat 调用同一个服务,比如 stock是 100,那么各个tomcat里获取到的 也是100 ,会造成数据重复 超卖的现象。

 

 

 

 

 

public void add(SubjectSettingAddReq req) {
       synchronized(this){
           int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
           if(stock > 0){
               int realStock = stock -1;
               stringRedisTemplate.opsForValue().set("stock",realStock + "");
               System.out.println("扣减成功,剩余库存:" + realStock + "");
           }else{
               System.out.println("扣减失败,库存不足");
           }
       }
}

 

 

 

Redis 命令

SETNX key value

只在键 key 不存在的情况下,将键 key的 值设置为 value.

若键 key已存在,则 SETNX命令不做任何动作。

SETNX 是 【SET if Not eXists】(如果不存在,则SET)的简写。

返回值:命令设置成功返回1,设置失败返回0。

 

 

 

demo

1. SETNX 原理 加 redis锁

public void add(SubjectSettingAddReq req) {
        // 获取锁,如果程序突然挂了,为了避免程序因为中断而造成一直加锁的情况产生,1秒钟后,key值失效,自动释放锁,
        // lock = stringRedisTemplate.opsForValue().setIfAbsent(KEY, LOCK); 
        // stringRedisTemplate.expire(KEY,10000, TimeUnit.MILLISECONDS);
        //以上 两段 写在一起 保证程序的原子性,reids 改成原子操作
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(KEY, LOCK).expire(KEY,10000, TimeUnit.MILLISECONDS); 
        if(lock){
            // 获取到锁,则查询 最大的sort
            int sort = subjectSettingWriteMapper.selectMaxSort(req);           
            //获取锁之后 业务逻辑处理            
        } 
    }

2. 针对程序异常 死锁的问题,加上redis超时时间

public void add(SubjectSettingAddReq req) {
    try{
        // 获取锁,如果程序突然挂了,为了避免程序因为中断而造成一直加锁的情况产生,1秒钟后,key值失效,自动释放锁,
        // lock = stringRedisTemplate.opsForValue().setIfAbsent(KEY, LOCK); 
        // stringRedisTemplate.expire(KEY,10000, TimeUnit.MILLISECONDS);
        //以上 两段 写在一起 保证程序的原子性,reids 改成原子操作
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(KEY, LOCK).expire(KEY,10000, TimeUnit.MILLISECONDS);
        if(lock){
            // 获取到锁,则查询 最大的sort
            int sort = subjectSettingWriteMapper.selectMaxSort(req);         
            //获取锁之后 业务逻辑处理
          }
         }finally{
                // 无论如何,最终都要释放
                stringRedisTemplate.delete(KEY);
        }
}

 

 

3. 以上可以应付一般的并发量,但是在高并发情况,可能导致 锁 永久失效

上面代码设置了1万毫秒(10s)自动对该key解锁,

如果第1个线程由于慢查询的原因需要15s 走完 该代码,其中 try中代码10s,finally中 5s;

第2个线程走完该代码 需要8s,其中 try中代码5s,finally中代码3s;

在高并发情况下,1线程进入 finall时 已经自动对该key解锁,2线程 才开始 对该key上锁 ;

1线程进入 finally 中对该key解锁时 实际是对 2线程 中上锁的key解锁,以此类推。。。 会导致锁 永久失效。

解决方法:用reisson 框架,原理 -- 主线程 开启一个分线程(后台线程)将锁延迟 三分之一 时间 再释放

// 加锁以后10秒钟自动解锁
// 无需调用unlock方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
 
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       lock.unlock();
   }

 

public void add(SubjectSettingAddReq req) {
    RLock redisLock = redisson.getLock(KEY);
    try{
        // 获取锁,如果程序突然挂了,为了避免程序因为中断而造成一直加锁的情况产生,1秒钟后,key值失效,自动释放锁,
        // lock = stringRedisTemplate.opsForValue().setIfAbsent(KEY, LOCK); 
        // stringRedisTemplate.expire(KEY,10000, TimeUnit.MILLISECONDS);
        //以上 两段 写在一起 保证程序的原子性,reids 改成原子操作
        //Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(KEY, LOCK).expire(KEY,10000, TimeUnit.MILLISECONDS);
        redisLock.lock(10, TimeUnit.SECONDS));
        if(!lock){
           //return error;
        }
        // 获取到锁,则查询 最大的sort
        int sort = subjectSettingWriteMapper.selectMaxSort(req);           
        //获取锁之后 业务逻辑处理   
        }finally{
            redisLock.unlock();
            // 无论如何,最终都要释放
            //stringRedisTemplate.delete(KEY);
}
}

(要注意redis 主从架构 redis 锁失效的问题:Redis(Master) 中锁 失效,但还未同步到 Redis(Slave) 中,导致其他线程过来时拿不到锁)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值