使用线程池优化sdk调用流程

场景:
  现有一个人脸检测算法sdk包(C++),使用java中的jni调用该sdk提供出java可直接调用的接口,并打包成jar包(作为依赖包)供业务系统调用。

  人脸算法执行流程为:(1)加载lisence -> (2)初始化FaceDetect、FaceAttribute 等对象 -> (3)传入图片数据进行检测 。 其中第一步加载lisence在整个系统的生命周期中,只需要加载一次就行。第二步需要创建的对象比较大。

  初始情况:直接调用jar包接口,每次业务系统调用都会进行加载lisence,创建初始化FaceDetect、FaceAttribute对象。这样造成接口执行耗时久,大对象频繁创建销毁。

优化:
(1)依赖包中的算法执行流程,封装成一个Callable<FaceAttrInfo[]>,可以给线程池调用,使用Callable接口是因为需要返回算法执行后识别出的人脸结果。
使用线程池优化sdk调用流程

(2)继承Thread,定制自己的线程,在线程初始时,加载lisence,创建FaceDetect、FaceAttribute 对象。
使用线程池优化sdk调用流程

(3)在业务系统调用处,使用ThreadPoolExecutor 创建线程池,其中通过ThreadFactory参数指定我们要创建的线程为上述第二步自定义的线程。

使用线程池优化sdk调用流程

效果:
优化后,接口耗时降低了接近60%,主要是由于每次调用接口,都不需要创建对象,而是直接使用线程中提前创建好的对象。其中对象的生命周期是依赖于线程的。同时这样做也提升了该接口的并发能力。

线程池这里有两个需要注意的点:
(1)人脸检测算法属于计算密集型任务,所以线程池创建的核心线程数大致应该为N+1(N为cpu核数),这里细究可以参考这篇回答:java线程池大小为何会大多被设置成CPU核心数+1?

(2)线程池的拒绝策略,这里不可以使用ThreadPoolExecutor.CallerRunsPolicy 而是使用默认的ThreadPoolExecutor.AbortPolicy。因为CallerRunsPolicy会让主线程来执行被抛弃的任务。而主线程中却没有FaceDetect、FaceAttribute等对象,从而无法执行,会导致程序出错。
使用AbortPolicy策略,抛弃任务,并抛出异常,由上层去处理。