假设程序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里面,从而保证死锁不会发生