导入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
代码实现
import com.google.common.collect.Lists;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.LockSupport;
/**
* 分布式锁
*/
@Component
public class DistributedLock {
public static final String LOCK_PREFIX = "member_lock:";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final RedisScript<Boolean> lockScript = new DefaultRedisScript<>(
"if redis.call('exists',KEYS[1]) == 1 then " +
" return false; " +
"end " +
"if(redis.call('set',KEYS[1],ARGV[1],'EX',ARGV[2],'NX')) " +
"then " +
" return true;" +
"else " +
" return false;" +
"end", Boolean.class);
private final RedisScript<Boolean> unlockScript = new DefaultRedisScript<>(
"if redis.call('get',KEYS[1]) == ARGV[1] " +
"then" +
" redis.call('del',KEYS[1]) " +
"end", Boolean.class);
/**
* 获取指定任务的锁并运行指定的任务
*
* @param task 任务名称
* @param runnable 任务内容
* @param acquireInterval 锁请求的间隔时长
* @param waitTimeout 锁等待超时时长,负数表示永不超时
* @param taskTimeout 任务运行超时时长(锁占用时长)
* @param timeUnit 时间单位
* @throws TimeoutException 锁等待超时时抛出此异常
*/
public void run(String task, Runnable runnable, long acquireInterval
, long waitTimeout, long taskTimeout, TimeUnit timeUnit) throws TimeoutException {
run(task, () -> {
runnable.run();
return null;
}, acquireInterval, waitTimeout, taskTimeout, timeUnit);
}
/**
* 获取指定任务的锁并运行指定的任务
*
* @param task 任务名称
* @param callable 任务内容
* @param acquireInterval 锁请求的间隔时长,负数表示无间隔时长
* @param waitTimeout 锁等待超时时长,负数表示永不超时
* @param taskTimeout 任务运行超时时长(锁占用时长)
* @param timeUnit 时间单位
* @return 任务返回值
* @throws TimeoutException 锁等待超时时抛出此异常
*/
public <V> V run(String task, Callable<V> callable, long acquireInterval
, long waitTimeout, long taskTimeout, TimeUnit timeUnit) throws TimeoutException {
if (StringUtils.isBlank(task)) {
throw new IllegalArgumentException("任务名称不能为空");
}
if (callable == null) {
throw new IllegalArgumentException("任务执行内容不能为Null");
}
if (taskTimeout < 1) {
throw new IllegalArgumentException("任务运行时长不能小于1");
}
if (timeUnit == null) {
throw new IllegalArgumentException("时间单位不能为Null");
}
String lockKey = LOCK_PREFIX + task;
String lockValue = UUID.randomUUID().toString();
List<String> keys = Lists.newArrayList(lockKey);
Boolean locked = Boolean.FALSE;
long waitTimeoutMillis = System.currentTimeMillis() + timeUnit.toMillis(waitTimeout);
try {
do {
locked = redisTemplate.execute(lockScript, keys,
lockValue, timeUnit.toSeconds(taskTimeout));
if (BooleanUtils.isNotTrue(locked)) {
if (waitTimeout >= 0 && System.currentTimeMillis() >= waitTimeoutMillis) {
throw new TimeoutException("Lock wait timeout:" + task);
}
if (acquireInterval > 0) {
LockSupport.parkNanos(timeUnit.toNanos(acquireInterval));
}
}
} while (BooleanUtils.isNotTrue(locked));
return callable.call();
} catch (TimeoutException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
if (BooleanUtils.isTrue(locked)) {
redisTemplate.execute(unlockScript, keys, lockValue);
}
}
}
}