一种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+线程池:
- Future<Integer> future =es.submit(calTask);
Future<T> = 线程池.submit(Callable<T>);
Future<T>.get();
(2) callable +futuretask+线程池
- FutureTask<Integer> futureTask=new FutureTask<>(calTask);
- //执行任务
- es.submit(futureTask);
线程池.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) |
455 | 179 |
422 | 152 |
422 | 120 |
450 | 102 |
517 | 92 |
480 | 88 |
另一个请求:
原 A(ms) | 现 B(ms) |
1217 | 216 |
584 | 184 |
505 | 171 |
503 | 116 |
556 | 96 |
线上60ms | 22ms |
可以看到,微观上响应速度提高了5倍
以腾讯压力测试:
A | B | |
20并发(4*4)2分钟 第一次 | tps:127.18 97.15ms | tps:212.98 57.57ms |
第二次 | tps:123.59 100.57ms | tps:208.77 57.50ms |
100并发 唯一一次 | tps:241.39 233.02ms | tps: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