基于redis实现分布式锁之不可重入导致死锁的解决


假设程序a获取到锁之后需要调用程序b,但是程序b需要使用锁,

但是这个时候程序a并没有执行完程序所以不能释放锁,但是程序b获取不到锁就没有办法执行,因此就出现了死锁

这样可以使用可重入锁解决(即判断是自己的锁则就可以再次获取锁)

existe 判断锁是否存在,hset 设置锁, expire 设置锁的到期时间  hincrby,设置锁的重入次数的递增

 

可重入锁加锁:

  1.判断锁是否被占用(exists lock),如果没有(0)被占用则直接获取锁(hset lock uuid 1)

  2.如果锁被占用了,则判断当前锁是否是当前线程占用了锁(判断是不是自己的锁)(hexists lock uuid),如果是(1)则重入(hincrby lock uuid 1)

  3.如果返回0,则说明获取锁失败

最后需要充值过期时间,因为程序a在执行期间很可能lock已经快过期,lock的剩余时间不足以等到b执行,因此最好重置下时间

lua脚本(使用lua脚本可以保证原子性)

  if redis.call('exists',KEYS[1])  == 0

  then 

    redis.call('hset',KEYS[1],ARVG[1],1)

    redis.call('expire',KEYS[1],ARVG[2])

    return 1

  elseif  redis.call('hexists',KEYS[1],ARVG[1])  == 1

  then 

    redis.call('hincrby', KEYS[1] ARVG[1], 1)

    redis.call('expire',KEYS[1],ARVG[2])

    retuen 1

  else 

    retuen 0

  end

lua脚本优化版

 

  if redis.call('exists',KEYS[1])  == 0  or redis.call('hexists',KEYS[1],ARVG[1])  == 1

  then 

    redis.call('hset',KEYS[1],ARVG[1],1)

    redis.call('expire',KEYS[1],ARVG[2])

    return 1

  else

    retuen 0

  end

 

KEYS:lock

ARGV: uuid 30

 

可重入锁解锁:

  lua脚本

  if redis,call('hexists' lock,uuid)  == 0

  then 

    return nil

  elseif redis.call('hincrby',lock,uuid , -1 )   == 0

  then 

    return redis.call('del' lock)

  else 

    return 0

  end


注:StringUtils.isBlank() :判断字符串是否为空 Collerction.isEmpty判断的的则是大小好像

 代码实现

  新建代码组件 

@Component
public class Distributedlock {

    @Autowired
    private StringRedisTemplate redisTemplate;
         public Boolean tryLock(String lockName,String uuid,Integer expire){   //锁的名称,uuid标识  到期时间
            String script = " if redis.call('exists',KEYS[1])  == 0  or redis.call('hexists',KEYS[1],ARVG[1])  == 1" +
                    "  then " +
                    "    redis.call('hset',KEYS[1],ARVG[1],1)" +
                    "    redis.call('expire',KEYS[1],ARVG[2]) " +
                    "       return 1 " +
                    " else " +
                    "retuen 0 " +
                    "end";
             Boolean flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString());
             if (!flag){
                 try {
                     Thread.sleep(50);
                     tryLock(lockName,uuid,expire);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             return true;
         }

         public void unlock(String lockName,String uuid){

             String script = "lua脚本" +
                     "" +
                     "  if redis,call('hexists' KEYS[1],ARVG[1]) == 0" +
                     "  then " +
                     "    return nil" +
                     "  elseif redis.call('hincrby',KEYS[1],ARVG[1] , -1 )  == 0" +
                     "  then " +
                     "    return redis.call('del' KEYS[1])" +
                     "  else " +
                     "    return 0" +
                     "  end";
             Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
             //  DefaultRedisScript<>(需要执行的脚本,返回值类型)
             if (flag == null){
                 throw new RuntimeException("释放的锁不属于你");
             }
         }

}

代码调用

 public void testlock2(){
        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.distributedlock.tryLock("lock", uuid, 30);
        if (lock){

        String num = this.redisTemplate.opsForValue().get("num");
        if (StringUtils.isBlank(num)){
            this.redisTemplate.opsForValue().set("num","1");
            return;
        }
        int i = Integer.parseInt(num);//把字符串转化为数值
            this.redisTemplate.opsForValue().set("num",String.valueOf(++i));
            this.distributedlock.unlock("lock",uuid);
        }
    }

最后的解锁应该定义在final里面,从而保证死锁不会发生

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM