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