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

本文介绍如何利用Java的Future模式提高程序性能,通过实例演示并行处理List中的元素,减少响应时间,并提供了一种封装方式以简化多线程操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

业务中经常碰到查询一个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.youkuaiyun.com/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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值