什么是分布式锁?
像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 "获取锁成功";
}