Java并行执行任务的几种方案小结

目录
  • 背景
  • 改进方案
  • 注意点和区别
  • java并行执行任务demo

背景

最近在排查生产环境问题,发现商品详情接口时不时会报RPC调用超时,检查代码发现接口里面查询活动耗时比较长,都是串行执行的,仔细查看发现完全可以改成并行去执行,缩短接口查询耗时。

比如我们的商品详情接口,需要展示立减、阶梯满减、团购等活动标签。需要查询三次不同的活动信息,再组装活动标签信息。

如果每次查询耗时1s,按照串行的方式去调用,整个接口下来至少需要3s,整个耗时,对于我们来讲是无法接受的。其实在jdk中,给我们提供了几种非常便捷的并行执行任务的方法。

  • CountDownLatch
  • ExecutorService.invokeAll()
  • Fork/Join 分而治之 有点类似MapReduce的影子,这个有兴趣的可以自行去了解

改进方案

代码例子:

    private void assemblyActivityTag(CartItemDTO itemDTO){
        //1.查询立减活动信息,耗时1s
        //2.查询阶梯满减活动信息,耗时1s
        //3.查询团购活动信息,耗时1s
        //4.组装活动标签信息,耗时1s
        // 串行执行下来整个耗时4s
    }

CountDownLatch

    private void assemblyActivityTag(CartItemDTO itemDTO){
        ExecutorService executorService = Executors.newCachedThreadPool();
        CountDownLatch latch = new CountDownLatch(3);
        executorService.execute(new Runnable() {
            @Override
            public void run() {
            //1.查询立减活动信息
                latch.countDown();
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //2.查询阶梯满减活动信息
                latch.countDown();
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //3.查询团购活动信息
                latch.countDown();
            }
        });
        try {
            // 一定记得加上timeout时间,防止阻塞主线程
            latch.await(3000,TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //4.等待所有子任务完成,组装活动标签信息

        //5.关闭线程池
        executorService.shutdown();
    }

ExecutorService.invokeAll()

private void assemblyActivityTag(CartItemDTO itemDTO) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Callable<String>> tasks = Lists.newArrayList();
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //1.查询立减活动信息
                return null;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //2.查询阶梯满减活动信息
                return null;
            }
        });
        tasks.add(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //3.查询团购活动信息
                return null;
            }
        });
        try {
            List<Future<String>> futureList = executorService.invokeAll(tasks, 3000, TimeUnit.MILLISECONDS);
            for (Future<String> future : futureList) {
                // 获取线程执行结果
                try {
                    String activityTag = future.get();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //4.组装活动标签信息
        //5.关闭线程池
        executorService.shutdown();
    }

注意点和区别

在使用CountDownLatch,尽可能使用线程安全的容器去处理子线程的返回值,避免多线程情况下,出现脏数据。

如果想知道每个子线程的对应的返回值,ExecutorService.invokeAll()方式,是没法区分的,只能依赖返回值的顺序去匹配。

使用上面2种方式时,切记设置超时时间,防止子任务执行时间过长,阻塞主线程任务

线程池用完结束,记得shutdown()

java并行执行任务demo

在一个方法中同时调用多个方法或者服务,并等待所有结果返回

package com.test.demo;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
public class TestFuture {
    @Test
    public void testA(){
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> c());
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> a());
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> b());
        try {
            //获取并行执行任务结果
            System.out.println(future3.get());
            System.out.println(future1.get());
            System.out.println(future2.get());
        }catch (Exception e){
        }
    }
    public String a(){
        try {
            Thread.sleep(1000);
        }catch (Exception e){
        }
        return "a";
    }
    private String b(){
        try {
            //模拟业务执行时间
            Thread.sleep(2000);
        }catch (Exception e){
        }
        return "b";
    }
    private String c(){
        try {
            //模拟业务执行时间
            Thread.sleep(5000);
        }catch (Exception e){
        }
        return "c";
    }
}

测试结果:

从执行结果中可以看到一共耗时5s,如果同步进行执行,耗时应该在8s

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java 多线程有序执行的几种方法总结

    Java 多线程有序执行的几种方法总结 同事无意间提出了这个问题,亲自实践了两种方法.当然肯定还会有更多更好的方法. 方法一 import java.util.concurrent.atomic.AtomicInteger; public class OrderedThread1 { static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws Interrupte

  • Java并行处理的实现

    目录 1. 背景 2.知识 3. Java 中的并行处理 4. 扩展 线程池方式实现并行处理 使用 fork/join框架 5.参考: 1. 背景 本文是一个短文章,介绍Java 中的并行处理. 说明:10多分钟读完的文章我称之为短文章,适合快速阅读. 2.知识 并行计算(parallel computing)一般是指许多指令得以同时进行的计算模式.在同时进行的前提下,可以将计算的过程分解成小部分,之后以并发方式来加以解决. 也就是分解为几个过程: 1.将一个大任务拆分成多个子任务,子任务还可以

  • SpringBoot 多任务并行+线程池处理的实现

    前言 前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑.当然了,优化是无止境的,前人栽树后人乘凉.作为我们开发者来说,既然站在了巨人的肩膀上,就要写出更加优化的程序. SpringBoot开发案例之JdbcTemplate批量操作 SpringBoot开发案例之CountDownLatch多任务并行处理 改造 理论上讲,线程越多程序可能更快,但是在实际使用中我们需要考虑到线程本身的创建以及销毁的资源消耗,以及保护操作系统本身的目的.我们通常需要将线程限制在一定

  • Java并行执行任务的几种方案小结

    目录 背景 改进方案 注意点和区别 java并行执行任务demo 背景 最近在排查生产环境问题,发现商品详情接口时不时会报RPC调用超时,检查代码发现接口里面查询活动耗时比较长,都是串行执行的,仔细查看发现完全可以改成并行去执行,缩短接口查询耗时. 比如我们的商品详情接口,需要展示立减.阶梯满减.团购等活动标签.需要查询三次不同的活动信息,再组装活动标签信息. 如果每次查询耗时1s,按照串行的方式去调用,整个接口下来至少需要3s,整个耗时,对于我们来讲是无法接受的.其实在jdk中,给我们提供了几

  • java解析XML几种方式小结

    java解析XML几种方式小结 第一种:DOM. DOM的全称是Document Object Model,也即文档对象模型.在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称DOM树),应用程序正是通过对这个对象模型的操作,来实现对XML文档数据的操作.通过DOM接口,应用程序可以在任何时候访问XML文档中的任何一部分数据,因此,这种利用DOM接口的机制也被称作随机访问机制. DOM接口提供了一种通过分层对象模型来访问XML文档信息的方式,这些分层对象模型依

  • 基于Vue SEO的四种方案(小结)

    前言:众所周知,Vue SPA单页面应用对SEO不友好,当然也有相应的解决方案,下面列出几种最近研究和使用过的SEO方案,SRR和静态化基于Nuxt来说. 1.SSR服务器渲染: 2.静态化: 3.预渲染prerender-spa-plugin: 4.使用Phantomjs针对爬虫做处理. 1.SSR服务器渲染 关于服务器渲染:Vue官网介绍 ,对Vue版本有要求,对服务器也有一定要求,需要支持nodejs环境. 使用SSR权衡之处: 开发条件所限,浏览器特定的代码,只能在某些生命周期钩子函数

  • java抛出异常的几种情况小结

    目录 1. 编译错误 2.可以执行 3. 抛出异常,不执行 常见运行时异常: 常见非运行时异常: Java中异常问题(异常抛出后是否继续执行的问题) 1. 编译错误 //代码1 public static void test() throws Exception { throw new Exception("参数越界"); System.out.println("异常后"); //编译错误,「无法访问的语句」 } 2.可以执行 //代码2 try{ throw ne

  • java List去掉重复元素的几种方式(小结)

    使用LinkedHashSet删除arraylist中的重复数据(有序) LinkedHashSet是在一个ArrayList删除重复数据的最佳方法.LinkedHashSet在内部完成两件事: 删除重复数据 保持添加到其中的数据的顺序 List<String> words= Arrays.asList("a","b","b","c","c","d"); HashSet<

  • Java实现监听文件变化的三种方案详解

    目录 背景 方案一:定时任务 + File#lastModified 方案二:WatchService 方案三:Apache Commons-IO 小结 背景 在研究规则引擎时,如果规则以文件的形式存储,那么就需要监听指定的目录或文件来感知规则是否变化,进而进行加载.当然,在其他业务场景下,比如想实现配置文件的动态加载.日志文件的监听.FTP文件变动监听等都会遇到类似的场景. 本文给大家提供三种解决方案,并分析其中的利弊,建议收藏,以备不时之需. 方案一:定时任务 + File#lastModi

  • java定义二维数组的几种写法(小结)

    如下所示: //定义二维数组写法1 class numthree { public static void main(String[] args) { float[][] numthree; //定义一个float类型的2维数组 numthree=new float[5][5]; //为它分配5行5列的空间大小 numthree[0][0]=1.1f; //通过下标索引去访问 1行1列=1.1 numthree[1][0]=1.2f; // 2行1列=1.2 numthree[2][0]=1.3

  • Java解决No enclosing instance of type PrintListFromTailToHead is accessible问题的两种方案

    今天在编译Java程序时遇到如下问题: No enclosing instance of type PrintListFromTailToHead is accessible. Must qualify the allocation with an enclosing instance of type PrintListFromTailToHead (e.g. x.new A() where x is an instance of PrintListFromTailToHead). 源代码为:

  • 基于Java数组实现循环队列的两种方法小结

    用java实现循环队列的方法: 1.添加一个属性size用来记录眼下的元素个数. 目的是当head=rear的时候.通过size=0还是size=数组长度.来区分队列为空,或者队列已满. 2.数组中仅仅存储数组大小-1个元素,保证rear转一圈之后不会和head相等.也就是队列满的时候.rear+1=head,中间刚好空一个元素. 当rear=head的时候.一定是队列空了. 队列(Queue)两端同意操作的类型不一样: 能够进行删除的一端称为队头,这样的操作也叫出队dequeue: 能够进行插

  • java实现时间控制的几种方案

    前言:最近小王同学又遇到了一个需求:线上的业务运行了一段时间,后来随着使用人数增多,出现了一个问题是这样的,一个订单会重复创建几次,导致数据库里出现了很多垃圾数据.在测试同学的不断测试下,发现问题出在了前端的一个提交按钮上,有的用户比较"着急",提交订单时候,一下子快速的点了5.6次(这手速没有几年是练不出来的,咳咳),导致请求一下子都涌进来,后端进行了重复处理.项目经理让小王优化这个点,绝对不能创建多次.还有一个需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,

随机推荐