spring boot使用CountDownLatch+@Async配合案例

文章描述了一个项目中处理PDF转图片上传的问题,通过使用CountDownLatch和Async异步多线程技术,成功提升了50%以上的响应速率,解决了在PDF页数多时接口响应慢的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

项目为影像文件系统,包含普通图片上传、视频上传和pdf转图片上传,在使用过程中我们发现在组合上传的时候pdf在页数比较多的情况下此接口响应速度缓慢,由此想到使用CountDownLatch+@Async异步多线程来处理pdf转图片操作,据测试可提升50%以上响应速率。

首先给PDF拆分图片加入@Async注解实现异步调用

    @Async
    public void pdfToPicVo(PDFInfoVo pdfInfoVo, Integer maxSort, PDDocument document, float dpi,String fname, String businessId,
            ArrayList<PicReqVo> picReqVos, CountDownLatch countDownLatch,int pageCount, PPictureBaseBean pictureBaseBean) {

        long startTime = System.currentTimeMillis();
        List<PicReqVo> resultPicListVos = new ArrayList<>();
        int sortNum = 0;
        try {
            PDFRenderer renderer = new PDFRenderer(document);

            /**
             * 设置一个值,指示渲染器在绘制之前是否允许对图像进行子采样。
             * 根据图像尺寸和请求的比例确定子采样频率。在某些情况下,子采样可能会更快且内存占用更少,
             * 但也可能导致质量损失,尤其是在具有高空间频率的图像中。
             * 形参: 允许二次采样的新值。
             */
            renderer.setSubsamplingAllowed(true);

            /*征信报告的PDF拆分完会比其他的模糊,这里征信报告的PDF倍率调大点*/
            if(PictureConstant.QJ_ZXREPORT_MENUCODE.equals(pdfInfoVo.getMenuCode())){
                LogUtil.textInfo("[影像服务][影像数据处理]PDF文件处理,征信报告目录增加倍率。businessId:{0}",businessId);
                dpi= 155;
            }

            for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
                String imageName = fname + "_" +  (pageIndex + 1) + "." + PDFtoPicFormat;
                /** 根据页码获取pdf拆分的图片流 */

                BufferedImage bufferedImage = null;
                try {
                    bufferedImage = renderer.renderImageWithDPI(pageIndex, dpi);
                } catch (Exception e) {
                    LogUtil.textWarn("[影像服务][影像PDF数据处理]可能是带有签章信息PDF拆分失败,将直接转存到磁盘。businessId:{0},文件名:{1}",businessId,fname);
                    sortNum = maxSort + pageIndex + 1;
                    float sort = (float) sortNum;
                    qjMediaSaveBiz.splitPdfFailedSaveFile(businessId,pdfInfoVo.getPdfPath() ,pdfInfoVo.getMenuCode(),fname,sort,pictureBaseBean.getPictureDetailTable());
                    countDownLatch.countDown();
                    return;
                }
                //检查PDF最后一页是否为空白页
                if ( pageCount-1 == pageIndex  && checkPicIsBlank(bufferedImage)){
                    continue;
                }
                String picBase64 = getBufferedImageToBase64(bufferedImage, null);
                bufferedImage = null;
                PicReqVo picReqVo = new PicReqVo();
                picReqVo.setFileShowname(imageName);
                sortNum = maxSort + pageIndex + 1;
                picReqVo.setSortNo((float) sortNum);
                picReqVo.setBase64Str(picBase64);
                picReqVo.setOperateType(PictureConstant.OPERATETYPE_ADD);
                picReqVo.setPictureFormat(PDFtoPicFormat);
                picReqVo.setMenuCode(pdfInfoVo.getMenuCode());
                picReqVo.setFileName(imageName);
                picReqVo.setMediaType(MediaTypeEnum.MEDIA_TYPE_PICTURE.getValue());
                resultPicListVos.add(picReqVo);
            }
            long endTime = System.currentTimeMillis();
            long total = (endTime - startTime) / 1000;
            LogUtil.textInfo("[影像服务][影像数据处理]PDF文件处理完成-文件名:{0} ,businessId:{1} ,耗时/秒:{2} ,线程名:{3}" , fname ,businessId,total,Thread.currentThread().getName());
            countDownLatch.countDown();
        } catch (Exception e) {
            LogUtil.textError("[影像服务][影像数据处理]PDF文件处理异常-文件名:{0},businessId:{1} ,异常信息:{2}" , fname ,businessId,LogUtil.ExceptionToString(e));
            throw new PictureServiceException(PictureException.ERR_100000);
        }finally {
            if ( document != null) {
                try {
                    document.close();
                } catch (IOException e) {
                    LogUtil.textError("[影像服务][影像数据处理]关闭流异常:{0}" , LogUtil.ExceptionToString(e));
                }
            }
        }
        picReqVos.addAll(resultPicListVos);
    }

这样就实现了异步请求接口,效率上提升了很多。但是,由于异步调用的原因,数据还没填充完就会返回,这显然不是我们想要的效果。我们必须等待线程结束后,再返回当前调用线程任务的方法。于是就想到了JDK1.5版本后提供的计数器CountDownLatch。

思路:

在Service层的方法中实例化CountDownLatch并且制定线程个数,线程个数就是从本地数据库查询的list的长度,并且传入线程任务中,每个线程执行完毕就调用countDown()方法。最后在Service层中调用await()方法。这样在线程计数为零之前,Service的线程就会一直等待。

 long startTime = System.currentTimeMillis();
 CountDownLatch countDownLatch = new CountDownLatch(pdfInfoVos.size());
//异步处理PDF,转为pic信息
pdfToPicVo(businessId, pictureBaseBean, picReqVos, maxSortNo, pdfInfoVos, menuMaxSortNo, countDownLatch);
countDownLatch.await(); //等待线程执行完毕
long endTime = System.currentTimeMillis();
long totaltime = (endTime - startTime) / 1000;
LogUtil.textInfo("[影像服务][AMC房押贷影像数据处理]该单号所有PDF文件处理完成-businessId:{0} ,总耗时/秒:{1}" , businessId ,totaltime);

问题解决(使用记录一下,有错误的地方还希望大家给建议)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值