手写分布式锁
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
package com.dmg.demo.config;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
//实现可重入锁
public class RedisLock implements Lock {
private StringRedisTemplate redisTemplate;
private String lockName;
private String uuid;
//默认过期时间
private long expire=60;
//有参构造注入
public RedisLock(StringRedisTemplate redisTemplate, String lockName, String uuid) {
this.redisTemplate = redisTemplate;
this.lockName = lockName;
this.uuid =uuid+":"+Thread.currentThread().getId();;
}
@Override
public void lock() {
this.tryLock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
return this.tryLock(-1L,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if(time!=-1){
//转换成秒
this.expire=unit.toSeconds(time);
}
//开始加锁
String script="if redis.call('exists',KEYS[1])==0 or redis.call('hexists',KEYS[1],ARGV[1])==1 " +
"then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2])" +
" return 1 else return 0 end ";
//uuid 默认传当前线程的id+uuid 看是不是当前线程的uuid 当前的uuid可能重复 多个线程进来 所以保证唯一就是加线程id
while (!redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName)
,uuid,String.valueOf(expire))){
//如果加锁的结果为0 就是false 那么等待重试 存在锁 但是 是别的线程的锁 不是我的线程的锁 所以等待重试 阻塞 等其他线程走完了
//我才能加锁成功 就是uuid不一样 不能重入 如果一样就是重入了一次 又更新了下过期时间
Thread.sleep(100);
}
//最后加锁成功
return true;
}
@Override
public void unlock() {
//释放锁
String script="if redis.call('hexists',KEYS[1],ARGV[1])==0 then return nil " +
"elseif redis.call('hincrby',KEYS[1],ARGV[1],-1)==0 then" +
" return redis.call('del',KEYS[1]) else return 0 end ";
Long res=redisTemplate.execute(new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockName),uuid);
if(res==null){
//抛出异常 异常解锁
throw new IllegalMonitorStateException("异常解锁");
}
}
@Override
public Condition newCondition() {
return null;
}
public void autoRenew(){
//自动续期
new Timer().schedule(new TimerTask() {
@Override
public void run() {
String script="if redis.call('hexists',KEYS[1],ARGV[1])==1" +
" then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";
boolean flag= redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class)
,Arrays.asList(lockName),uuid,String.valueOf(expire));
if(flag){
//如果续期成功 就一直续期 如果续期失败 那么这个方法就是结束了 不需要在续期了
autoRenew();
}
//当前过期时间到了3分之1的时候 在进行续期
}
}, expire*1000/3);
}
}
//单例工厂
@Component
public class RedisLockClient {
@Autowired
private StringRedisTemplate redisTemplate;
private String uuid;
private RedisLockClient(){
//在无参方法里面初始化uuid 多个对象调用 只返回一个一样的uuid
uuid= UUID.randomUUID().toString();
}
//这里传入之后 对方就不用在加注解了
public RedisLock getRedisLock(String lockName){
return new RedisLock(redisTemplate,lockName, uuid);
}
}
//单例工厂
@Autowired
private RedisLockClient redisLockClient;
@Autowired
private StringRedisTemplate redisTemplate;
public void aaa(String 商品id){
//注意 这里一定要结合商品id去加锁,如果不加,别的商品进来的 你也给人家锁住了
String lockName="lock1:"+商品id;
RedisLock redisLock=redisLockClient.getRedisLock(lockName);
//加锁
redisLock.lock();
try {
//自动续期 防止 方法还没有走完 锁就过期了
redisLock.autoRenew();
//查询库存
String stock=redisTemplate.opsForValue().get("stock").toString();
//是否够用
if(StringUtils.isEmpty(stock)){
return;
}
int num=Integer.parseInt(stock);
if(num>0){
//减去库存
num--;
redisTemplate.opsForValue().set("stock",String.valueOf(num));
}
}finally {
//释放锁
redisLock.unlock();
}
}
配置文件
spring.redis.host=192.168.184.145
spring.redis.port=6379
使用Redisson实现分布式锁
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.1</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
// 配置对象
Config config = new Config();
//单节点 地址 redis://ip:6379
config.useSingleServer().setAddress("redis://192.168.184.132:6379");
//创建配置
return Redisson.create(config);
}
}
@Autowired
private RedissonClient redissonClient;
public void aa(String 商品id){
//注意 这里一定要结合商品id去加锁,如果不加,别的商品进来的 你也给人家锁住了
String lockName="lock1:"+商品id;
RLock rLock=redissonClient.getLock(lockName);
//加锁 默认有自动续期的功能,默认时间30秒,当前时间达到3分之1的时候,自动续期
//底层就是继承了juc的Lock类,也实现了可重入锁
rLock.lock();
try {
//查询库存
String stock=redisTemplate.opsForValue().get("stock").toString();
//是否够用
if(StringUtils.isEmpty(stock)){
return;
}
int num=Integer.parseInt(stock);
if(num>0){
//减去库存
num--;
redisTemplate.opsForValue().set("stock",String.valueOf(num));
}
}finally {
//释放锁
rLock.unlock();
}
}
在使用分布式锁的时候,要吧事务放在外面,不能在分布式锁哪个方法加事务,例如分布式锁在内掉了一个修改的方法,在哪个修改的方法里面去加事务,如果在分布式锁加了事务,会造成可重复读的现象