这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
前言
本次功能点需要解决的问题是使用 websocket 完成批量生成中的页面进度条加载显示问题。
本次的难点是:
- 1、针对大数据下的批量生成采用多线程进行优化
- 2、通过websocket 推送进度条给前端
- 3、前端通过订阅并展示进度条
本次页面展示的是简易版,后续还会持续优化,但是功能已经基本实现了。
一、数据并发处理
在调用后端接口的时候,这里是需要异步处理的。返回给前端响应,然后异步处理业务逻辑。后面的进度条任务就交给websocket 来监听处理了。
而这个异步处理任务的时候,我们需要用到线程池去处理,但是这又有个问题,我们需要等待所有线程执行结束之后做个最后的动作,把剩余的list 更新到数据库中。而这就需要判断所有的线程是否执行完毕。因为主线程是自上往下执行的,它并不需要等待你异步的执行结果就往下执行了,所以,这块需要等待。
1.1 线程池
根据上述分析,代码逻辑是这样的,通过线程池进行处理
// 1、异步线程处理
new Thread(() -> {
for (DeviceInfo deviceInfo : deviceInfos) {
pool.execute(new Runnable() {
@Override
public void run() {
// 2、执行业务逻辑
}).start();
// 3、返回响应
return ResponseData.successResponse();
复制代码
1.2 如何计算百分比
此时,根据经验,在并发处理一个数的时候,我们需要使用到原子类,
可以在循环外面定义一个 原子类 ic ,然后在循环体内对齐进行自增操作,与总数做除法运算
DecimalFormat df = new DecimalFormat();
df.setMaximumFractionDigits(2);//设置保留几位小数
df.setMinimumFractionDigits(2);
AtomicInteger ai = new AtomicInteger(0);
String result = df.format(ai.incrementAndGet() * 100.00 / amout);
复制代码
1.3 如何等待所有线程执行结束
在所有线程执行结束之后,我们需要把剩余的数据更新到数据库中,而等待线程执行结束的方法常用的有以下几种:
1.3.1 通过pool 的isTerminated() 方法
例如下面的例子:
public class Test {
public static void main(String args[]) throws InterruptedException {
ExecutorService exe = Executors.newFixedThreadPool(50);
for (int i = 1; i <= 5; i++) {
exe.execute(new SubThread(i));
}
exe.shutdown();
while (true) {
if (exe.isTerminated()) {
System.out.println("结束了!");
break;
}
Thread.sleep(200);
}
}
复制代码
当调用 exe.shutdown() 方法时,线程池不再接收任何新任务,此时线程池并不会立刻退出,直到线程池中的任务都已经处理完成,才会退出,然后弄个 while(true) 监听 isTerminated 方法来判断是否线程池中的所有线程都已经执行完毕,然后执行最后的逻辑。
理论上,可以用这个来实现我这功能,但是测试了一波之后发现无法处理我的业务,就没采用了。
1.3.2 CyclicBarrier 循环屏障
循环屏障 在使用过之后才发现,它字面意思是个可循环的屏障。它要做的事情就是让一组线程到达一个屏障时被阻塞,知道最后一个线程到达屏障时,然后执行下一步操作,然后清0,继续到达下一个屏障。它内部的计数器是可以被重新执行的。
因此,它是可以流处理的,每次线程执行到一个屏障的时候就 入一次库。理论上是可以做到的,但是为了不压垮数据库,也不采纳。
1.3.3 CountDownLatch 闭锁
它是一个同步工具类,用来协调多个线程之间的同步。它的作用是,在完成某些运算时,只有其他所有线程的运算全部完成,当前线程的运算才能继续执行
其实它就是一个计数器,每个线程来一次就把总值减一,最后到0 的时候在执行最后的逻辑。
因为我们的总值是可以获取到的,所以可以创建出同等大小的计数器,具体代码如下:
// 1、创建一个计数器
CountDownLatch countDownLatch = new CountDownLatch(amout);
for (DeviceInfo deviceInfo : deviceInfos) {
pool.execute(new Runnable() {
@Override
public void run() {
//2、处理业务逻辑,然后减一
countDownLatch.countDown();
}
});
}
try {
// 3、等待,知道计数器为0 的时候执行后续代码
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4、插入剩余list
if (list.size() != 0) {
deviceInfoMapper.updateByBatch(list);
list.clear();
}
复制代码
二、websocket 使用
websocket 这块直接调用的这个方法,是本地封装的,用到了 SimpMessagingTemplate ,有兴趣的可以百度下怎么集成。
@PostMapping("/sendMsg")
public Boolean sendMsg(@RequestBody SocketDTO socketDTO) {
if (checkCertificate(socketDTO.getPassword())) {
log.error("socket account or password Incorrect -> password = {}", socketDTO.getPassword());
return false;
}
simpMessagingTemplate.convertAndSend(socketDTO.getTopic(), socketDTO.getData());
log.debug("socket send success -> topic = {} , data = {}", socketDTO.getTopic(), JSONObject.toJSONString(socketDTO.getData()));
return true;
}
复制代码
而,我们需要用的就是调用 websocket 发送消息 百分比数据 给前端即可
socketApi.sendMsg(SocketDTO
.builder()
.password(configComponent.getPassword())
.topic(ElectricityStationConst.DEVICE_BATCH_PRODUCE + sysId)
.data(result)
.build()
);
复制代码
三、前端订阅展示百分比
<el-progress :text-inside="true" :stroke-width="26" :percentage="processvalue" v-show="processshow" ></el-progress>
mounted() {
this.init()
this.stomp()
},
methods:{
stomp(){
this.$bus.on(TopicConst.DEVICE_BATCH_PRODUCE+StoreService.getStationId(),data =>{
this.$data.processshow=true
this.$data.processvalue=parseInt(data)
if(this.$data.processvalue==100){
this.$message.success("批量打印完成");
this.$data.processshow=false
}
})
// 批量生成二维码关系
productBatch(row){
console.log(StoreService.getStationId())
this.$api.req("/am/device/img/produceBatch",row,res=>{
this.$message.success("二维码批量生成中,请稍等.....")
this.$stomp.sub(TopicConst.DEVICE_BATCH_PRODUCE+StoreService.getStationId(),data =>{
this.$bus.emit(TopicConst.DEVICE_BATCH_PRODUCE+StoreService.getStationId(),data)
})
this.$data.loading = false;
this.$data.processshow=true;
},res=>{
this.$message.error(res.msg)
})
},
},
复制代码
不过,vue 的进度条好像只能展示整数,而不能传入小数的,不然控制台会出现很多提醒,因此这里我又把值转成了整数。
最后,终于实现了这一小小的功能,这一过程确实成长了不少。