您的位置:首页 > 其它

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

2018-01-11 10:58 363 查看
业务中经常碰到查询一个list后又需要对list进行后续处理(在sql层面不方便处理),需要循环遍历list
如果list中各item的业务独立,可用future模式来大大提高性能

1.关于future模式的概念
参考:彻底理解Java的Future模式  https://www.cnblogs.com/cz123/p/7693064.html
先上一个场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。但是,子线程执行的结果是要返回厨具的,而run方法是没有返回值的。所以,这才是难点,需要好好考虑一下。

2.关于实现
(1) callable+future+线程池:

Future<Integer> future =es.submit(calTask);  

callable无法直接start,需要借助线程池
Future<T> = 线程池.submit(Callable<T>);
Future<T>.get();

(2)   callable +futuretask+线程池

FutureTask<Integer> futureTask=new FutureTask<>(calTask);  
        //执行任务  
        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查询

 
355308311275271
205178119105849196

455422422450517480
2501791521201029288
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: