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.1port: 6379
@Configuration
public class RedissonConfig {@Autowiredprivate RedisProperties redisProperties;@Beanpublic 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:";@Overridepublic <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();}}@Overridepublic <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();}}@Overridepublic <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();}}@Overridepublic <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();}}@Overridepublic <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 "获取锁成功";}