一种Furture模式处理请求中循环独立的任务的方法

业务中经常碰到查询一个list后又需要对list进行后续处理(在sql层面不方便处理),需要循环遍历list

如果list中各item的业务独立,可用future模式来大大提高性能



1.关于future模式的概念

参考:彻底理解Java的Future模式  https://www.cnblogs.com/cz123/p/7693064.html

先上一个场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。

实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。

但是,子线程执行的结果是要返回厨具的,而run方法是没有返回值的。所以,这才是难点,需要好好考虑一下。




2.关于实现

(1) callable+future+线程池:

  1. Future<Integer> future =es.submit(calTask);  
callable无法直接start,需要借助线程池

Future<T> = 线程池.submit(Callable<T>);

Future<T>.get();


(2)   callable +futuretask+线程池

  1. FutureTask<Integer> futureTask=new FutureTask<>(calTask);  
  2.         //执行任务  
  3.         es.submit(futureTask);  
FutureTask<T> = new FutureTask<T>(Callable<T>);

线程池.submit(FutureTask<T>)

FutureTask<T>.get();


(3)   callable +futuretask+Thread

同时FureTask也实现了runnable,可以直接允许不用线程池

FutureTask<Class> ft = new FutureTask<Class>((Callable<Class>) 
Thread thread = new Thread(ft);
thread.start;

FutureTask<T> = new FutureTask<T>(Callable<T>);

Thread = new Thread(FutureTask<T>);

Thread.start();

FutureTask<T>.get();


参考:Java多线程编程:Callable、Future和FutureTask浅析(多线程编程之四)     博客比较深入

http://blog.csdn.net/javazejian/article/details/50896505  



3. 应用

业务模型是这样的:

List<T> = sql;

for(T : list<T>) {
     sql(T);
     T.doSomething();
}

以future模式重构

List<T> = sql;

List<FutureTask<T>> taskList = new ArrayList<FutureTask<T>>(list.size());

for(T t: list<T>) {

    FutureTask<T>  f = new FutureTask<T>(new TaskCallable<T>(t));      

    taskList.add(f);

    Thread thread = new Thread(f);

    thread.start();

}

for(FutureTask<T> ft : taskList) {

    ft.get();

}

将T分为多个线程并行处理,同时等待所有计算结果,整合后返回

这种模式在业务中达到以下效果:

原(ms)现(ms)
455179
422152
422120
450102
51792
48088
  


另一个请求:

原 A(ms)现 B(ms)
1217216
584184
505171
503116
55696
线上60ms22ms
  

可以看到,微观上响应速度提高了5倍

以腾讯压力测试:

 AB
20并发(4*4)2分钟 第一次tps:127.18   97.15mstps:212.98    57.57ms
第二次tps:123.59  100.57mstps:208.77   57.50ms
100并发  唯一一次tps:241.39  233.02mstps:256.48   209.21ms
不处理   唯一一次     tps:793.77  71.93ms  
   
   
分析:

20并发下,性能差不多差一倍

100并发下,差距不大了,推测为mysql顶不住了,证明这种方案在高压下不可取,原本一次查询变成了n+1次,应尽量避免


4.封装

虽然原则上予以避免,力求在单次sql层面解决,但迫于需求频繁修改,由于这种情况比较常见,故封装一下:

@Service
public class FurtureService<T> {

    /**
     *
     * @param o   外部类对象,用于取得某对象的内部类
     * @param list    待循环多线程处理的数据
     * @param c    内部Callable类型
     * @param args    内部类参数 数组
     */
    public void run(Object o, List<T> list, Class c, Object [] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, ExecutionException, InterruptedException {

        List<FutureTask<Class>> taskList = new ArrayList<FutureTask<Class>>(list.size());
    //    ExecutorService exec = Executors.newFixedThreadPool(list.size());

        Constructor [] constructor = null;

        constructor = c.getConstructors();

        for(int i=0; i<list.size(); ++i) {
            T t = list.get(i);
            FutureTask<Class> ft = new FutureTask<Class>((Callable<Class>) constructor[0].newInstance(o, list.get(i), args));
            taskList.add(ft);
        //    exec.submit(ft);
            Thread thread = new Thread(ft);
            thread.start();
        }

        for (FutureTask<Class> ft : taskList) {
            ft.get();
        }
    //    exec.shutdown();

    }

}

调用:

    @Autowired
    private FurtureService<T> furtureService;

furtureService.run(this, list, ComputeTask.class, new Object[]{para});

其实还是有优点的:


1.提高了cpu使用率

2.一定程度弥补了不能在数据库层面一并取出数据的性能


缺点:

1.数据库压力倍增,1次sql变成了n+1次sql查询

 


355

308

311

275

271


205

178

119

105

84

91

96



455

422

422

450

517

480


250

179

152

120

102

92

88


原文地址:https://www.cnblogs.com/silyvin/p/9106705.html