实战篇-09.分布式锁-基本原理和不同实现方式对比_哔哩哔哩_bilibili
因为jvm内部的sychonized锁无法在不同jvm之间共享锁监视器,所以需要一个jvm外部的锁来共享。
加锁解锁即可
redis 的setnx不会自动释放锁,要是加锁后服务宕机,锁得不到释放可能死锁。
所以需要给锁加过期时间。
用set + 参数的方式同时设置锁和过期时间,保证不会因为过期时间没来及设置就宕机导致死锁
最终版本 :
到此为止基本完成了分布式锁,但是还可以加以改进
一般用非阻塞式,阻塞式浪费cpu而且实现麻烦。
阻塞式就是发现别人用锁,就一直等待。
非阻塞式就是别人拿锁我就返回。
?
private String name; //业务名字
private StringRedisTemplate stringRedisTemplate;
private static final String KEY_PREFIX = "lock:"; //规范名字
private static final String ID_PREFIX = UUID.randomUUID() + "-";
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean tryLock(long timeoutSec) {
//获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
//获取锁 set key value NX EX 过期时间
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success); //防拆箱空指针
}
在订单创建业务那里把sychnoized锁改成自己实现的分布式锁(获取+解锁)
业务1阻塞时间太长,导致锁过期自动删除,
?每个jvm内部的线程号是一种递增的数字,但是不同的jvm之间线程号可能冲突,所以需要找一种方法 区分不仅jvm内部而且jvm之间的线程。
uuid是一种唯一识别码,能保证不同的服务(jvm)的uuid一定不一样。
所以用 uuid + jvm内部线程id的方式来唯一标识所有jvm中的线程
小科普:通用唯一标识码UUID的介绍及使用 - 知乎 (zhihu.com)
实战篇-15.分布式锁-Lua脚本解决多条命令原子性问题_哔哩哔哩_bilibili
调用redis提供的call函数,传入redis命令参数
为了传参而把参数位留空后:
?提前读取好lua文件,避免频繁读取,等会调用。
为了维持 释放锁时 判断线程id和释放锁操作的原子性,重写unlcok方法
package com.hmdp.utils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class SimpleRedisLock implements ILock {
private String name; //业务名字
private StringRedisTemplate stringRedisTemplate;
private static final String KEY_PREFIX = "lock:"; //规范名字
private static final String ID_PREFIX = UUID.randomUUID() + "-";
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean tryLock(long timeoutSec) {
//获取线程标示
String threadId = ID_PREFIX + Thread.currentThread().getId();
//获取锁 set key value NX EX 过期时间
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success); //防拆箱空指针
}
@Override
public void unlock() {
//调用lua脚本
stringRedisTemplate.execute(UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId()
);
}
/*
*
@Override
public void unLock() {
//获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
//获取锁中的标识
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
//判断标识是否一致
if (threadId.equals(id)) {
//释放锁
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
* */
}