背景
项目为影像文件系统,包含普通图片上传、视频上传和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);
问题解决(使用记录一下,有错误的地方还希望大家给建议)。