SpringBoot多线程生产者消费者模型应用——排队叫号实验模拟(一)
1. 需求说明
目前的需求是在web端做一个排队叫号系统的过程模拟,目前实现了前半部分,使用到了生产者消费者模型,虽然比较简单,但还是记录一下。
2. 目前实现进度
完成了Thread A放客户到缓冲区,Thread B从缓冲区取客户并放入redis队列的过程。
实现效果图:
3.关键代码
3.1 缓冲区实现
因为客户有时间标签,每个人的标签基本上不一样,所以缓冲区考虑并发的优先级队列。
@Component
public class PatientTimeSeqBuffer {
private static final PriorityBlockingQueue<PatientDto> BUFFER_QUEUE = new PriorityBlockingQueue<>(200);
// 放入缓存区
public void putPatientDto2Buffer(PatientDto patient) {
try {
BUFFER_QUEUE.put(patient);
}catch (Exception e) {
e.printStackTrace();
}
}
// 取出优先级最高的元素
public PatientDto getPatientDto() {
try {
return BUFFER_QUEUE.take();
}catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
3.2 生产者Async流程
@Autowired
private PatientInfoService patientInfoService; // 查客户信息
@Autowired
private PatientTimeSeqBuffer buffer; // 缓冲池
@Async("arriveTaskExecutor")
@Override
public void timeSleepPut2Buffer(long startTime, List<PatientDto> patientDtoList) {
while (!patientDtoList.isEmpty()) { // 给的总列表不空,就一直准备出队
PatientDto dto = patientDtoList.remove(0); // 移除队首
Long addBufferTime = dto.getCurrentTime(); // 以下皆为业务代码
if (addBufferTime - startTime > 0) {
try {
Thread.sleep(addBufferTime - startTime); // 这里写的不好,但没想到别的解决方案,这里线程池最多只有一个线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer.putPatientDto2Buffer(dto);
log.info("{}进入了缓冲区at{}", dto, new Date());
}
}
3.3 消费者Async流程
@Autowired
private PatientInfoService patientInfoService;
@Autowired
private WeightedQueuing algorithmService;
@Autowired
private final QueueUtil queueUtil;
public AsyncServiceImpl(QueueUtil queueUtil) {
this.queueUtil = queueUtil;
}
@Async("computeTaskExecutor")
@Override
public void computeNextRoom() {
// 依靠别的线程来打断自己
while (true) {
// 先从缓存区中取客户,若无客户则阻塞自己
PatientDto patientDto = buffer.getPatientDto();
// 计算
PatientInfo patientInfo = patientInfoService.getById(patientDto.getPatientId());
RoomInfo result = algorithmService.computeNextRoom(patientInfo);
// 准备插入数据
PatientVo vo = new PatientVo(patientInfo);
synchronized (queueUtil) {
queueUtil.addPatient2Queue(result.getRoomCode(), vo);
log.info("{} 去 {}, 在{}时", patientInfo.getPatientName(), result.getRoomName(), new Date());
}
}
}
3.4 PatientDto及测试代码
/*
* PatientDto.java
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PatientDto implements Comparable<PatientDto>{
private Long patientId;
private Integer queueNum;
private String patientName;
private String patientGender;
private Long currentTime;
public PatientDto(PatientVo vo, long time) {
this.patientId = vo.getPatientId();
this.queueNum = vo.getQueueNum();
this.patientName = vo.getPatientName();
this.patientGender = vo.getPatientGender();
this.currentTime = time;
}
public PatientDto(PatientInfo info, long time) {
this.patientId = info.getPatientId();
this.queueNum = info.getQueueNum();
this.patientName = info.getPatientName();
this.patientGender = info.getPatientSex();
this.currentTime = time;
}
@Override
public String toString() {
return "PatientDto{" +
patientId +
", " + patientName +
", " + currentTime +
'}';
}
/**
* 令时间早的优先级高
* @param o
* @return
*/
@Override
public int compareTo(PatientDto o) {
int compareTo = this.currentTime.compareTo(o.currentTime);
compareTo = -compareTo;
return compareTo;
}
}
/*
* 测试Controller方法
*/
@GetMapping("/simu/start")
public Result ScheduleService() {
QueryWrapper qw = new QueryWrapper(); // 筛选时间字段
qw.eq("appointment_time", "2022-02-26 07:00:00");
List<PatientInfo> patientInfoList = patientInfoService.list(qw);
// 制作随机间隔时间
// TODO:随机间隔时间可以更贴近现实
Random random = new Random();
LongStream longs = random.longs(patientInfoList.size(), 1000L, 3000L);
long[] randomTimes = longs.toArray();
List<PatientDto> patientDtoList = new LinkedList<>();
long startTime = System.currentTimeMillis(); // 实验开始时间记录
log.info("实验开时间是: {}", startTime);
// 装载dto对象,时间为当前时间加上随机时间
for (int i = 0; i < patientInfoList.size(); i++) {
patientDtoList.add(new PatientDto(patientInfoList.get(i), startTime + randomTimes[i]));
}
// 开始异步执行加入缓冲区操作
asyncService.timeSleepPut2Buffer(startTime, patientDtoList);
// 开始异步执行计算并分配, 希望分配的时候同一时刻仅有一个在分配
asyncService.computeNextRoom();
// 开启websocket发送定时任务
asyncService.websocketScheduling();
return Result.success();
}
线程池配置及其他业务解释略.
4. 结语
自己今天终于勉强客服拖延症,尝试做五分钟,然后真正投入状态,另外不要给自己太大压力,该睡觉睡觉,该娱乐娱乐,把控好学习、放松和健身的度。目前自己技术水平还是很菜,只能做一个拼合怪。基础方面,数据结构算法还没怎么刷题,操作系统网络几乎没看,java八股看了一点多线程也看不下去了。希望这周开始是个充实而平衡的一周。晚安。
5. 参考文章
1.深入理解 JUC:PriorityBlockingQueue
2.Java并发编程之PriorityBlockingQueue阻塞队列详解
3. csdn——PriorityBlockingQueue使用小结
4.生产者消费者模式——BlockingQueue
5.SpringBoot定时任务+自定义线程池
6.spring boot:使用多个线程池实现实现任务的线程池隔离(spring boot 2.3.2)