这是java高并发系列第27篇文章。

开发环境:jdk1.8。

案例讲解

电商app都有用过吧,商品详情页,需要给他们提供一个接口获取商品相关信息:

  1. 商品基本信息(名称、价格、库存、会员价格等)
  2. 商品图片列表
  3. 商品描述信息(描述信息一般是由富文本编辑的大文本信息)

数据库中我们用了3张表存储上面的信息:

  1. 商品基本信息表:t_goods(字段:id【商品id】、名称、价格、库存、会员价格等)
  2. 商品图片信息表:t_goods_imgs(字段:id、goods_id【商品id】、图片路径),一个商品会有多张图片
  3. 商品描述信息表:t_goods_ext(字段:id,goods_id【商品id】、商品描述信息【大字段】)

这需求对于大家来说很简单吧,伪代码如下:

public Map<String,Object> detail(long goodsId){
//创建一个map
//step1:查询商品基本信息,放入map
map.put("goodsModel",(select * from t_goods where id = #gooldsId#));
//step2:查询商品图片列表,返回一个集合放入map
map.put("goodsImgsModelList",(select * from t_goods_imgs where goods_id = #gooldsId#));
//step3:查询商品描述信息,放入map
map.put("goodsExtModel",(select * from t_goods_ext where goods_id = #gooldsId#));
return map;
}

上面这种写法应该很常见,代码很简单,假设上面每个步骤耗时200ms,此接口总共耗时>=600毫秒,其他还涉及到网络传输耗时,估计总共会在700ms左右,此接口有没有优化的空间,性能能够提升多少?我们一起来挑战一下。

在看一下上面的逻辑,整个过程是按顺序执行的,实际上3个查询之间是没有任何依赖关系,所以说3个查询可以同时执行,那我们对这3个步骤采用多线程并行执行,看一下最后什么情况,代码如下:

package com.itsoku.chat26;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*; /**
* 跟着阿里p7学并发,微信公众号:javacode2018
*/
public class Demo1 { /**
* 获取商品基本信息
*
* @param goodsId 商品id
* @return 商品基本信息
* @throws InterruptedException
*/
public String goodsDetailModel(long goodsId) throws InterruptedException {
//模拟耗时,休眠200ms
TimeUnit.MILLISECONDS.sleep(200);
return "商品id:" + goodsId + ",商品基本信息....";
} /**
* 获取商品图片列表
*
* @param goodsId 商品id
* @return 商品图片列表
* @throws InterruptedException
*/
public List<String> goodsImgsModelList(long goodsId) throws InterruptedException {
//模拟耗时,休眠200ms
TimeUnit.MILLISECONDS.sleep(200);
return Arrays.asList("图1", "图2", "图3");
} /**
* 获取商品描述信息
*
* @param goodsId 商品id
* @return 商品描述信息
* @throws InterruptedException
*/
public String goodsExtModel(long goodsId) throws InterruptedException {
//模拟耗时,休眠200ms
TimeUnit.MILLISECONDS.sleep(200);
return "商品id:" + goodsId + ",商品描述信息......";
} //创建个线程池
ExecutorService executorService = Executors.newFixedThreadPool(10); /**
* 获取商品详情
*
* @param goodsId 商品id
* @return
* @throws ExecutionException
* @throws InterruptedException
*/
public Map<String, Object> goodsDetail(long goodsId) throws ExecutionException, InterruptedException {
Map<String, Object> result = new HashMap<>(); //异步获取商品基本信息
Future<String> gooldsDetailModelFuture = executorService.submit(() -> goodsDetailModel(goodsId));
//异步获取商品图片列表
Future<List<String>> goodsImgsModelListFuture = executorService.submit(() -> goodsImgsModelList(goodsId));
//异步获取商品描述信息
Future<String> goodsExtModelFuture = executorService.submit(() -> goodsExtModel(goodsId)); result.put("gooldsDetailModel", gooldsDetailModelFuture.get());
result.put("goodsImgsModelList", goodsImgsModelListFuture.get());
result.put("goodsExtModel", goodsExtModelFuture.get());
return result;
} public static void main(String[] args) throws ExecutionException, InterruptedException {
long starTime = System.currentTimeMillis();
Map<String, Object> map = new Demo1().goodsDetail(1L);
System.out.println(map);
System.out.println("耗时(ms):" + (System.currentTimeMillis() - starTime));
}
}

输出:

{goodsImgsModelList=[图1, 图2, 图3], gooldsDetailModel=商品id:1,商品基本信息...., goodsExtModel=商品id:1,商品描述信息......}
耗时(ms):208

可以看出耗时200毫秒左右,性能提升了2倍,假如这个接口中还存在其他无依赖的操作,性能提升将更加显著,上面使用了线程池并行去执行3次查询的任务,最后通过Future获取异步执行结果。

整个优化过程:

  1. 先列出无依赖的一些操作
  2. 将这些操作改为并行的方式

用到的技术有:

  1. 线程池相关知识
  2. Executors、Future相关知识

总结

  1. 对于无依赖的操作尽量采用并行方式去执行,可以很好的提升接口的性能
  2. 大家可以在你们的系统中试试这种方法,感受一下效果,会让你感觉很爽

java高并发系列目录

  1. 第1天:必须知道的几个概念
  2. 第2天:并发级别
  3. 第3天:有关并行的两个重要定律
  4. 第4天:JMM相关的一些概念
  5. 第5天:深入理解进程和线程
  6. 第6天:线程的基本操作
  7. 第7天:volatile与Java内存模型
  8. 第8天:线程组
  9. 第9天:用户线程和守护线程
  10. 第10天:线程安全和synchronized关键字
  11. 第11天:线程中断的几种方式
  12. 第12天JUC:ReentrantLock重入锁
  13. 第13天:JUC中的Condition对象
  14. 第14天:JUC中的LockSupport工具类,必备技能
  15. 第15天:JUC中的Semaphore(信号量)
  16. 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能
  17. 第17天:JUC中的循环栅栏CyclicBarrier的6种使用场景
  18. 第18天:JAVA线程池,这一篇就够了
  19. 第19天:JUC中的Executor框架详解1
  20. 第20天:JUC中的Executor框架详解2
  21. 第21天:java中的CAS,你需要知道的东西
  22. 第22天:JUC底层工具类Unsafe,高手必须要了解
  23. 第23天:JUC中原子类,一篇就够了
  24. 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
  25. 第25天:掌握JUC中的阻塞队列

java高并发系列连载中,总计估计会有四五十篇文章。

阿里p7一起学并发,公众号:路人甲java,每天获取最新文章!

java高并发系列 - 第27天:实战篇,接口性能成倍提升,让同事刮目相看,现学现用的更多相关文章

  1. java高并发系列 - 第31天:获取线程执行结果,这6种方法你都知道?

    这是java高并发系列第31篇. 环境:jdk1.8. java高并发系列已经学了不少东西了,本篇文章,我们用前面学的知识来实现一个需求: 在一个线程中需要获取其他线程的执行结果,能想到几种方式?各有 ...

  2. java高并发系列 - 第32天:高并发中计数器的实现方式有哪些?

    这是java高并发系列第32篇文章. java环境:jdk1.8. 本文主要内容 4种方式实现计数器功能,对比其性能 介绍LongAdder 介绍LongAccumulator 需求:一个jvm中实现 ...

  3. Java高并发系列——检视阅读

    Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...

  4. java高并发系列-第1天:必须知道的几个概念

    java高并发系列-第1天:必须知道的几个概念 同步(Synchronous)和异步(Asynchronous) 同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后, ...

  5. java高并发系列 - 第6天:线程的基本操作

    新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...

  6. java高并发系列 - 第12天JUC:ReentrantLock重入锁

    java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...

  7. java高并发系列 - 第14天:JUC中的LockSupport工具类,必备技能

    这是java高并发系列第14篇文章. 本文主要内容: 讲解3种让线程等待和唤醒的方法,每种方法配合具体的示例 介绍LockSupport主要用法 对比3种方式,了解他们之间的区别 LockSuppor ...

  8. java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能

    这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...

  9. java高并发系列 - 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能

    这是java高并发系列第16篇文章. 本篇内容 介绍CountDownLatch及使用场景 提供几个示例介绍CountDownLatch的使用 手写一个并行处理任务的工具类 假如有这样一个需求,当我们 ...

随机推荐

  1. 安卓学习资料推荐《深入理解Android:卷2》下载

    下载地址:百度云下载地址 编辑推荐 <深入理解Android:卷2>编辑推荐:经典畅销书<深入理解Android:卷I>姊妹篇,51CTO移动开发频道和开源中国社区一致鼎力推荐 ...

  2. [开源]OSharpNS 步步为营系列 - 3. 添加业务服务层

    什么是OSharp OSharpNS全称OSharp Framework with .NetStandard2.0,是一个基于.NetStandard2.0开发的一个.NetCore快速开发框架.这个 ...

  3. 详叙BeanWrapper和PropertyDescriptor

    每篇一句 千古以来要饭的没有要早饭的,知道为什么吗? 相关阅读 [小家Spring]聊聊Spring中的数据转换:Converter.ConversionService.TypeConverter.P ...

  4. 【题解】长度为素数的路径个数-C++

    Description 对于正整数n (3≤n<20),可以画出n阶的回形矩阵.下面画出的分别是3阶的,4阶的和7阶的回形矩阵: 对于n阶回形矩阵,从左上角出发,每步可以向右或向下走一格,走2* ...

  5. 爬虫之突破xm-sign校验反爬

    喜马拉雅 网页分析 - 打开我们要爬取的音乐专辑https://www.ximalaya.com/ertong/424529/ - F12打开开发者工具 - 点击XHR 随便点击一首歌曲会看到存储所有 ...

  6. Netty-解码器架构与常用解码器

    任何数据类型想在网络中进行传输,都得经过编解码转换成字节流 在netty中,服务端和客户端进行通信的其实是下面这样的 程序 ---编码--> 网络 网路 ---解码--> 程序 对应服务端 ...

  7. Docker部署web环境之Lanmp

    1. 案例一 整套项目多容器分离通过docker-compose部署lanmp环境 中方文档参考网址: docker/kubernets网址 http://www.dockerinfo.net/doc ...

  8. 分析了京东内衣销售记录,告诉你妹子们的真Size!

    >今天闲暇之余写了一个爬虫例子.通过爬虫去爬取京东的用户评价,通过分析爬取的数据能得到很多结果,比如,哪一种颜色的胸罩最受女性欢迎,以及中国女性的平均size(仅供参考哦~) 打开开发者工具-n ...

  9. spark 源码分析之三 -- LiveListenerBus介绍

    LiveListenerBus 官方说明如下: Asynchronously passes SparkListenerEvents to registered SparkListeners. 即它的功 ...

  10. 实现一个Golang的reverse函数

    Reverse函数,用来反转列表,本例子用golang实现,反转一个slice列表. 因为slice是引用类型,因此直接修改参数的值即可. func myReverse(l []string) { f ...