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