在单机环境中,应用是同一进程下,我们只需要保证单进程多线程线程安全性即可,通过Java提供的volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一些线程安全的类等就可以做到。但是在多机部署的环境中,不同机器不同进程,就需要在多进程下保证线程安全性,因此,分布式锁应允而生
1:pom.xml添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.21.1</version>
</dependency>
2-1:方法一:读取默认yml配置
spring:
redis:
database: 0
host: 127.0.0.1
password: 123456
port: 6379
2-2:redisson.yml文件的方式
spring:
redis:
redisson:
file: classpath:redisson.yml
2-3:redisson.yml
singleServerConfig:
database: 0
address: redis://127.0.0.1:6379
password: 123456
clusterServersConfig:
nodeAddresses:
- "redis://127.0.0.1:16379"
- "redis://127.0.0.1:26379"
- "redis://127.0.0.1:36379"
password: 123456
3-1:方法二:自定义yml配置
spring:
redis:
redisson:
enabled: true
mode: single
address: redis://127.0.0.1:6379
password: 123456
database: 0
3-2:RedissonConfig自定义配置类
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(name = "spring.redis.redisson.enabled", havingValue = "true")
public class RedissonConfig {
@Value("${spring.redis.redisson.mode}")
private String mode;
@Value("${spring.redis.redisson.masterName:}")
private String masterName;
@Value("${spring.redis.redisson.address}")
private String address;
@Value("${spring.redis.redisson.password:}")
private String password;
@Value("${spring.redis.redisson.database:0}")
private Integer database;
@Bean
public RedissonClient redissonClient() {
if (StringUtils.isBlank(password)) {
password = null;
}
Config config = new Config();
if ("single".equals(mode)) {
config.useSingleServer()
.setDatabase(database)
.setPassword(password)
.setAddress(address);
} else if ("cluster".equals(mode)) {
String[] clusterAddresses = address.split(",");
config.useClusterServers()
.setPassword(password)
.addNodeAddress(clusterAddresses);
} else if ("sentinel".equals(mode)) {
String[] sentinelAddresses = address.split(",");
config.useSentinelServers()
.setDatabase(database)
.setPassword(password)
.setMasterName(masterName)
.addSentinelAddress(sentinelAddresses);
} else if ("master-slave".equals(mode)) {
String[] masterSlaveAddresses = address.split(",");
if (masterSlaveAddresses.length == 1) {
throw new IllegalArgumentException(
"redis.redisson.address MUST have multiple redis addresses for master-slave mode.");
}
String[] slaveAddresses = new String[masterSlaveAddresses.length - 1];
System.arraycopy(masterSlaveAddresses, 1, slaveAddresses, 0, slaveAddresses.length);
config.useMasterSlaveServers()
.setDatabase(database)
.setPassword(password)
.setMasterAddress(masterSlaveAddresses[0])
.addSlaveAddress(slaveAddresses);
} else {
throw new IllegalArgumentException(mode);
}
return Redisson.create(config);
}
}
4:demo
@Resource
private RedissonClient redissonClient;
private static final String LOCK_KEY = "myLock";
@SneakyThrows
@GetMapping("test")
public void test() {
RLock lock = redissonClient.getLock(LOCK_KEY);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
lock.lock(10, TimeUnit.SECONDS);
System.out.println(new Date()+Thread.currentThread().getName() + " 获取到锁,开始处理任务...");
Thread.sleep(2000);
System.out.println(new Date()+Thread.currentThread().getName() + " 处理任务完成,释放锁...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
}
Thread.sleep(10000);
System.out.println("===");
}
4-1:运行结果
5-1:自定义注解分布式锁JLock
package com.huan.study.mybatis.config;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface JLock {
LockModel lockModel() default LockModel.AUTO;
String[] lockKey() default {};
String keyConstant() default "";
long expireSeconds() default 30000L;
long waitTime() default 10000L;
String failMsg() default "获取锁失败,请稍后重试";
}
5-2:LockModel
package com.huan.study.mybatis.config;
public enum LockModel {
REENTRANT,
FAIR,
MULTIPLE,
REDLOCK,
READ,
WRITE,
AUTO
}
5-3:BaseAspect
package com.huan.study.mybatis.aspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class BaseAspect {
public List<String> getValueBySpEL(String key, String[] parameterNames, Object[] values, String keyConstant) {
List<String> keys = new ArrayList<>();
if (!key.contains("#")) {
String s = "redis:lock:" + key + keyConstant;
log.debug("lockKey:" + s);
keys.add(s);
return keys;
}
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], values[i]);
}
Expression expression = parser.parseExpression(key);
Object value = expression.getValue(context);
if (value != null) {
if (value instanceof List) {
List value1 = (List) value;
for (Object o : value1) {
addKeys(keys, o, keyConstant);
}
} else if (value.getClass().isArray()) {
Object[] obj = (Object[]) value;
for (Object o : obj) {
addKeys(keys, o, keyConstant);
}
} else {
addKeys(keys, value, keyConstant);
}
}
log.info("表达式key={},value={}", key, keys);
return keys;
}
private void addKeys(List<String> keys, Object o, String keyConstant) {
keys.add("redis:lock:" + o.toString() + keyConstant);
}
}
5-4:DistributedLockHandler
package com.huan.study.mybatis.aspect;
import com.huan.study.mybatis.config.JLock;
import com.huan.study.mybatis.config.LockModel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
public class DistributedLockHandler extends BaseAspect {
@Autowired(required = false)
private RedissonClient redissonClient;
@SneakyThrows
@Around("@annotation(jLock)")
public Object around(ProceedingJoinPoint joinPoint, JLock jLock) {
Object obj = null;
log.info("进入RedisLock环绕通知...");
RLock rLock = getLock(joinPoint, jLock);
boolean res = false;
long expireSeconds = jLock.expireSeconds();
long waitTime = jLock.waitTime();
if (rLock != null) {
try {
if (waitTime == -1) {
res = true;
rLock.lock(expireSeconds, TimeUnit.MILLISECONDS);
} else {
res = rLock.tryLock(waitTime, expireSeconds, TimeUnit.MILLISECONDS);
}
if (res) {
obj = joinPoint.proceed();
} else {
log.error("获取锁异常");
}
} finally {
if (res) {
rLock.unlock();
}
}
}
log.info("结束RedisLock环绕通知...");
return obj;
}
@SneakyThrows
private RLock getLock(ProceedingJoinPoint joinPoint, JLock jLock) {
String[] keys = jLock.lockKey();
if (keys.length == 0) {
throw new RuntimeException("keys不能为空");
}
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
Object[] args = joinPoint.getArgs();
LockModel lockModel = jLock.lockModel();
RLock rLock = null;
String keyConstant = jLock.keyConstant();
if (lockModel.equals(LockModel.AUTO)) {
if (keys.length > 1) {
lockModel = LockModel.REDLOCK;
} else {
lockModel = LockModel.REENTRANT;
}
}
if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.REDLOCK) && keys.length > 1) {
throw new RuntimeException("参数有多个,锁模式为->" + lockModel.name() + ".无法锁定");
}
switch (lockModel) {
case FAIR:
rLock = redissonClient.getFairLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0));
break;
case REDLOCK:
List<RLock> rLocks = new ArrayList<>();
for (String key : keys) {
List<String> valueBySpEL = getValueBySpEL(key, parameterNames, args, keyConstant);
for (String s : valueBySpEL) {
rLocks.add(redissonClient.getLock(s));
}
}
RLock[] locks = new RLock[rLocks.size()];
int index = 0;
for (RLock r : rLocks) {
locks[index++] = r;
}
rLock = new RedissonRedLock(locks);
break;
case MULTIPLE:
rLocks = new ArrayList<>();
for (String key : keys) {
List<String> valueBySpEL = getValueBySpEL(key, parameterNames, args, keyConstant);
for (String s : valueBySpEL) {
rLocks.add(redissonClient.getLock(s));
}
}
locks = new RLock[rLocks.size()];
index = 0;
for (RLock r : rLocks) {
locks[index++] = r;
}
rLock = new RedissonMultiLock(locks);
break;
case REENTRANT:
List<String> valueBySpEL = getValueBySpEL(keys[0], parameterNames, args, keyConstant);
if (valueBySpEL.size() == 1) {
rLock = redissonClient.getLock(valueBySpEL.get(0));
} else {
locks = new RLock[valueBySpEL.size()];
index = 0;
for (String s : valueBySpEL) {
locks[index++] = redissonClient.getLock(s);
}
rLock = new RedissonRedLock(locks);
}
break;
case READ:
rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).readLock();
break;
case WRITE:
rLock = redissonClient.getReadWriteLock(getValueBySpEL(keys[0], parameterNames, args, keyConstant).get(0)).writeLock();
break;
}
return rLock;
}
}
6:使用简单的依赖包实现分布式锁
6-1:引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
6-2:application.yml 配置文件
spring:
data:
redis:
database: 0
host: 192.168.11.50
password:
port: 6379
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1
timeout: 1000
6-3:示例代码
@RestController
public class RedisController {
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping(value = "/deduct_stock")
public String deductStock() {
redisTemplate.opsForValue().set("stock", String.valueOf(100));
for (int i = 0; i < 5; i++) {
CompletableFuture.runAsync(() -> {
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
redisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
});
}
return "success";
}
}