多线程并发争用问题场景
单体架构,只剩一个号源,多个用户同时挂号,怎么保证这个号源只被一个用户挂成功?
可以使用,同步关键字 synchronized,或者加锁 lock。
这个只适用于同一个JVM中,分布式不适用。分布式项目中,每个服务都有自己的JVM。
分布式架构,还剩10个号,100个用户同时请求,怎么保证数据只被10个人请求成功?
1)可以使用Redis的分布式锁 setnx;
2)命令语法如下:setnx key value;效果等同于 SET key value NX
3)代码表现形式如下:返回的是一个布尔值,true or false;Boolean isSuccess = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1");
4)添加锁后处理逻辑:
// 加分布式锁: setnx
Boolean isSuccess = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1");
isSuccess==true,抢锁成功的线程,继续后续业务处理。
比如:预约挂号项目升级为分布式架构,专家号一般只有少量,一百多万人同时预约专家号。此时可以添加Redis的分布式锁,对抢专家号源成功的线程,可进行正常的预约业务处理,并扣减专家号剩余号源数;
isSuccess==false,抢锁失败的线程,可以暂停20s进行递归重试。
最后记得在finally释放锁。
加锁
1、分布式架构-加分布式锁代码:
@RestController
public class TestController {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
/**
* @description 分布式线程抢券、抢单
* @author 邓林妹
* @date 2024/3/5 21:19
*/
@RequestMapping("/sale")
public String sale() {
String message = "";
String key = "redisLock";
//uuidValue==uuid:线程id
String uuidValue = UUID.randomUUID().toString().replace("-", "") + ":" + Thread.currentThread().getId();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
//flag=false,抢不到的线程要继续重试
if (flag == null || !flag) {
//暂停20s,进行递归重试
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//抢锁成功的线程,进行正常的业务逻辑处理,扣减库存
try {
//1、查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
//2、查询库存是否充足
Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
//3、扣减库存
if (inventoryNumber > 0) {
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
message = "成功卖出一个商品,库存剩余:" + inventoryNumber;
System.out.println(message + "\t" + "服务端口" + port);
} else {
message = "商品卖完了";
}
} finally {
//释放锁
stringRedisTemplate.delete(key);
}
}
return message;
}
2、单体架构-线程同步、加锁代码:
@RestController
public class TestController {
private Lock lock = new ReentrantLock();
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
//方法一:synchronized
public synchronized String sale2() {
//业务处理代码
return null;
}
//方法二:lock
public String sale() {
String message = "";
lock.lock();
try {
//1、查询库存信息
String result = stringRedisTemplate.opsForValue().get("inventory001");
//2、查询库存是否充足
Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
//3、扣减库存
if (inventoryNumber > 0) {
stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
message = "成功卖出一个商品,库存剩余:" + inventoryNumber;
System.out.println(message + "\t" + "服务端口" + port);
} else {
message = "商品卖完了";
}
} finally {
lock.unlock();
}
return message + "\t" + "服务端口" + port;
}
其它说明
使用 StringRedisTemplate 类操作 redis 时,需要引入的maven依赖。
<!--***** SpringBoot 整合 redis start ****-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--**spring-boot-starter-data-redis 默认采用 lettuce作为redis客户端,
lettuce 底层采用 netty实现, 可以在多个线程中并发访问,且线程安全,
在使用lettuce需要配置线程池**-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--***** SpringBoot 整合 redis end ****-->