Redis分布式锁简单实现

什么是分布式锁?

像synchronized、ReentrantLock这些都是Jvm锁,可以确保在单个jvm中,多线程访问共享资源安全性,当多个线程访问共享资源的时候,用 synchronized、ReentrantLock 将共享资源包裹起来,可确保共享资源只能被串行访问。

Redis分布式锁实现

1. 引入redisson配置

<!-- redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.1</version>
</dependency>

2. RedissonConfig

yaml配置

spring:
  redis:
    host: 127.0.0.1
    port: 6379
@Configuration
public class RedissonConfig {
    @Autowired
    private RedisProperties redisProperties;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
        .setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
        .setPassword(redisProperties.getPassword())
        .setDatabase(redisProperties.getDatabase());
        return Redisson.create(config);
    }
}

3. 接口实现类



/**
 * 分布式锁工具类
 */
public interface DistributeLock {

    /**
     * 尝试获取分布式锁,获取到锁后执行 supplier.get() 方法,然后释放锁,返回执行的返回值;若无法获取锁,抛出异常
     * 获取锁的过程不会阻塞
     * 锁会自动续期
     *
     * @param lockKey  上锁的key
     * @param supplier 需要执行的业务
     * @return 执行的返回值
     */
    <T>  T tryLockRun(String lockKey, Supplier<T> supplier);

    /**
     * 尝试在指定的时间内获取分布式锁,获取到锁后执行 supplier.get() 方法,然后释放锁,返回执行的返回值;若无法获取锁,抛出异常;此方法最多阻塞时长由waitTime指定
     * 获取锁的过程会阻塞,阻塞时长由 waitTime 指定
     * 锁会自动续期
     *
     * @param lockKey  上锁的key
     * @param supplier 需要执行的业务
     * @param waitTime 获取锁等待时间,必须大于 0
     * @param unit     时间单位
     * @return 执行的返回值
     */
    <T>  T tryLockRun(String lockKey, Supplier<T> supplier, long waitTime, TimeUnit unit);

    /**
     * 尝试在指定的时间内获取分布式锁,获取到锁后执行 supplier.get() 方法,然后释放锁,返回执行的返回值;若无法获取锁,抛出异常;此方法最多阻塞时长由waitTime指定
     * 获取锁的过程会阻塞,阻塞时长由 waitTime 指定
     * 超过了 leaseTime,若锁还未释放,则自动释放
     *
     * @param lockKey   上锁的key
     * @param supplier  需要执行的业务
     * @param waitTime  获取锁等待时间,必须大于 0
     * @param leaseTime 锁持有时间(必须大于 0:超过了这个时间,若未主动释放,则会自动释放)
     * @param unit      时间单位
     * @return 执行的返回值
     */
    <T>  T tryLockRun(String lockKey, Supplier<T> supplier, long waitTime, long leaseTime, TimeUnit unit) ;

    /**
     * 此方法会一直等待,直到获取锁后,获取到锁后执行  supplier.get() 方法,然后释放锁
     * 获取锁的过程会阻塞,直到成功获取锁
     * 锁会自动续期
     *
     * @param lockKey  上锁的key
     * @param supplier 需要执行的业务
     * @return 执行的返回值
     */
    <T>  T lockRun(String lockKey, Supplier<T> supplier);

    /**
     * 此方法会一直等待,直到获取锁后,获取到锁后执行  supplier.get() 方法,然后释放锁
     * 获取锁的过程会阻塞,直到成功获取锁
     * 超过了 leaseTime,若锁还未释放,则自动释放
     *
     * @param lockKey   上锁的key
     * @param supplier  需要执行的业务
     * @param leaseTime 锁持有时间(必须大于 0:超过了这个时间,若未主动释放,则会自动释放)
     * @param unit      时间单位
     * @return 执行的返回值      
     */
    <T>  T lockRun(String lockKey, Supplier<T> supplier, long leaseTime, TimeUnit unit);
}



@Service
@Slf4j
@RequiredArgsConstructor
public class DistributeLockImpl implements DistributeLock {

    private final RedissonClient redissonClient;

    // 锁前缀--可以加载配置里用来区分不同的业务
    private static final String LOCK_KEY_PREFIX = "lock:";

    @Override
    public <T> T tryLockRun(String lockKey, Supplier<T> supplier) {
        Objects.requireNonNull( supplier, "Supplier cannot be null");
        Objects.requireNonNull(lockKey, "Lock key cannot be null");
        String redisKey = LOCK_KEY_PREFIX+lockKey;
        RLock lock = redissonClient.getLock(redisKey);
        boolean  flag = lock.tryLock();
        if (!flag) {
            throw new RuntimeException("获取锁失败,锁被占用");
        }
        try {
            //执行锁内逻辑代码
            return supplier.get();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public <T> T tryLockRun(String lockKey, Supplier<T> supplier, long waitTime, TimeUnit unit) {
        Objects.requireNonNull( supplier, "Supplier cannot be null");
        Objects.requireNonNull(lockKey, "Lock key cannot be null");
        if (waitTime < 0){
            throw new IllegalArgumentException("Wait time should ge 0");
        }
        String redisKey = LOCK_KEY_PREFIX+lockKey;
        RLock lock = redissonClient.getLock(redisKey);
        try {
            boolean  flag = lock.tryLock(waitTime,unit);
            if (!flag) {
                throw new RuntimeException("获取锁失败,锁被占用");
            }
            //执行锁内逻辑代码
            return supplier.get();
        } catch (Exception e){
            throw new RuntimeException("获取锁异常->{}",e);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public <T> T tryLockRun(String lockKey, Supplier<T> supplier, long waitTime, long leaseTime, TimeUnit unit)  {
        Objects.requireNonNull( supplier, "Supplier cannot be null");
        Objects.requireNonNull(lockKey, "Lock key cannot be null");
        if (waitTime < 0){
            throw new IllegalArgumentException("Wait time should ge 0");
        }
        if (leaseTime < 0){
            throw new IllegalArgumentException("Lease time should ge 0");
        }
        String redisKey = LOCK_KEY_PREFIX+lockKey;
        RLock lock = redissonClient.getLock(redisKey);
        try {
            boolean  flag = lock.tryLock(waitTime,leaseTime,unit);
            if (!flag) {
                throw new RuntimeException("获取锁失败,锁被占用");
            }
            //执行锁内逻辑代码
            return supplier.get();
        } catch (Exception e){
            throw new RuntimeException("获取锁异常->{}",e);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public <T> T lockRun(String lockKey, Supplier<T> supplier) {
        Objects.requireNonNull( supplier, "Supplier cannot be null");
        Objects.requireNonNull(lockKey, "Lock key cannot be null");
        String redisKey = LOCK_KEY_PREFIX+lockKey;
        RLock lock = redissonClient.getLock(redisKey);
        lock.lock();
        try {
            //执行锁内逻辑代码
            return supplier.get();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public <T> T lockRun(String lockKey, Supplier<T> supplier, long leaseTime, TimeUnit unit) {
        Objects.requireNonNull( supplier, "Supplier cannot be null");
        Objects.requireNonNull(lockKey, "Lock key cannot be null");
        if (leaseTime < 0){
            throw new IllegalArgumentException("Lease time should ge 0");
        }
        String redisKey = LOCK_KEY_PREFIX+lockKey;
        RLock lock = redissonClient.getLock(redisKey);
        lock.lock(leaseTime,unit);
        try {
            //执行锁内逻辑代码
            return supplier.get();
        } catch (Exception e){
            throw new RuntimeException("获取锁异常->{}",e);
        } finally {
            lock.unlock();
        }
    }
}

4. 使用方法

 this.distributeLock.lockRun("lock1", () -> {
      //执行业务逻辑 
            retren null;
    });

测试类

1. 测试1

@GetMapping("/tryLockRun")
public String tryLockRun() {
    long startTime = System.currentTimeMillis();
    boolean lockResult = this.distributeLock.tryLockRun("lock1", () -> {
        log.info("获取锁成功,执行业务");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return true;
    });

    log.info("耗时:{},{}", (System.currentTimeMillis() - startTime), lockResult ? "获取锁成功" : "获取锁失败");
    return lockResult ? "获取锁成功" : "获取锁失败";
}

第一次调用获取到锁,执行任务,其它线程抢占报错

2. 测试2

@GetMapping("/tryLockRunWaitTime")
public String tryLockRunWaitTime() {
    long startTime = System.currentTimeMillis();
    log.info("tryLockRunWaitTime start");

 
    boolean lockResult = this.distributeLock.tryLockRun("lock1", () -> {
        log.info("获取锁成功,执行业务");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return true;
    }, 4, TimeUnit.SECONDS);

    log.info("耗时:{},{}", (System.currentTimeMillis() - startTime), lockResult ? "获取锁成功" : "获取锁失败");
    return lockResult ? "获取锁成功" : "获取锁失败";
}

3. 测试3

 @GetMapping("/lockRun")
    public String lockRun() {
        long startTime = System.currentTimeMillis();
        log.info("lockRun start");
        this.distributeLock.lockRun("lock1", () -> {
            log.info("获取锁成功,执行业务");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return true;
        });
        log.info("获取锁成功,耗时:{}", (System.currentTimeMillis() - startTime));
        return "获取锁成功";
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值