一.引言
先来看一些APP的获取数据,诸如你打开我的淘宝时,多达10条的用户行为数据,比如:收藏数,关注店铺,足迹,红包卡券,待付款,待发货,待收货,评价,退款/售后,真的是多~
平时要10+接口的去获取数据(因为当你10+个查询写一起,那估计到半分钟才能响应了),一个页面上N多接口,真是累死前端的宝宝了,今天我们也可以一个接口将这些数据返回~ 还贼TM快,解决串行编程,阻塞编程带来的苦恼~
二.串行执行任务,返回结果
2.1 pom.xml
新建一个Springboot项目,pom.xml如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>LingluoSpringdataJPA</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-jre</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
为了使读者更方便的使用本例,重点突出异步计算
,暂时不引入数据库,将用sleep模拟业务操作。
2.2 建一个实体类UserBehaviorDataDTO
package com.example.demo.entity;
import lombok.Builder;
import lombok.Data;
/**
* Created by Lingluo on 2020/1/28.
* @Builder:建造者模式
* @Data:集合了@ToString、@EqualsAndHashCode、@Getter/@Setter、@RequiredArgsConstructor的所有特性
*/
@Builder
@Data
public class UserBehaviorDataDTO {
private long unPayCount;//待付款
private long unDeliveryCount;//待发货
private long unReceiveCount;//待收货
private long evaluateCount;//评价
private long refundCount;//退款
}
这里使用了Lombok,它能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。
@Builder,使用了设计模式之建造者模式,定义为“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。”
建造者模式尤其适用于参数非常多的情况,比如本例,有10几个参数(这里只写了我的订单里的5个参数为例)。
2.3 定义一个用户接口类
package com.example.demo.service;
/**
* Created by Lingluo on 2020/1/28.
*/
public interface LingluoUserService {
long unPayCountByUserId(Long userId);
long unDeliveryCountByUserId(Long userId);
long unReceiveCountByUserId(Long userId);
long evaluateCountByUserId(Long userId);
long refundCountByUserId(Long userId);
}
2.4 实现类,用于处理真实业务操作
package com.example.demo.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class LingluoUserServiceImpl implements LingluoUserService {
//@Autowired
//UserMapper userMapper;
@Override
public long unPayCountByUserId(Long userId) {
try {
Thread.sleep(10000);
System.out.println("获取unPayCount===睡眠:" + 10 + "s");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("UserService获取unPayCount的线程 " + Thread.currentThread().getName());
return 10;
}
@Override
public long unDeliveryCountByUserId(Long userId) {
System.out.println("UserService获取unDeliveryCount的线程 " + Thread.currentThread().getName());
try {
Thread.sleep(10000);
System.out.println("获取unDeliveryCount===睡眠:" + 10 + "s");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 20;
}
@Override
public long unReceiveCountByUserId(Long userId) {
System.out.println("UserService获取unReceiveCount的线程 " + Thread.currentThread().getName());
try {
Thread.sleep(10000);
System.out.println("获取unReceiveCount==睡眠:" + 10 + "s");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 30;
}
@Override
public long evaluateCountByUserId(Long userId) {
System.out.println("UserService获取evaluateCount的线程 " + Thread.currentThread().getName());
try {
Thread.sleep(10000);
System.out.println("获取evaluateCountByUserId===睡眠:" + 10+ "s");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 40;
//return userMapper.evaluateCountByUserId(userId);
}
@Override
public long refundCountByUserId(Long userId) {
System.out.println("UserService获取refundCount的线程 " + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(4);
System.out.println("获取refundCount===睡眠:" + 4 + "s");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 50;
}
}
2.5 Controller调用
package com.example.demo.controller;
import com.example.demo.entity.LingluoUserBehaviorDataDTO;
import com.example.demo.service.LingluoUserService;
import com.example.demo.util.LingluoFutureTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by Lingluo on 2020/1/28.
*/
@RestController
@RequestMapping("/user")
public class LingluoUserController {
@Autowired
private LingluoUserService userService;
@Autowired
private LingluoFutureTask lingluoFutureTask;
@GetMapping("/index")
@ResponseBody
public String index() {
return "启动用户模块成功~~~~~~~~";
}
//http://localhost:8080/user/get/data?userId=1
@GetMapping("/get/data")
@ResponseBody
public LingluoUserBehaviorDataDTO getUserData(Long userId) {
System.out.println("UserController的线程:" + Thread.currentThread());
long begin = System.currentTimeMillis();
LingluoUserBehaviorDataDTO userAggregatedResult = lingluoFutureTask.getUserAggregatedResult(userId);
long end = System.currentTimeMillis();
System.out.println("===============总耗时:" + (end - begin) /1000.0000+ "秒");
return userAggregatedResult;
}
}
2.6 LingluoFutureTask 类
内部定义了一个线程池进行任务的调度和线程的管理以及线程的复用,大家可以根据自己的实际项目情况进行配置。
其中线程调度示例:核心线程 8 最大线程 20 保活时间30s 存储队列 10 有守护线程
拒绝策略:将超负荷任务回退到调用者
说明 : 默认使用核心线程(8)数执行任务,任务数量超过核心线程数就丢到队列,队列(10)满了就再开启新的线程,新的线程数最大为20,当任务执行完,新开启的线程将存活30s,若没有任务就消亡,线程池回到核心线程数量。
另外如果你不想每个类都使用Log4j
private final Logger logger = LoggerFactory.getLogger(当前类名.class);
可以使用@Slf4j注解
package com.example.demo.util;
import com.example.demo.entity.LingluoUserBehaviorDataDTO;
import com.example.demo.service.LingluoUserService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.*;
/**
* Created by Lingluo on 2020/1/28.
*/
@Slf4j
@Component
public class LingluoFutureTask {
@Resource
LingluoUserService userService;
/**
* 核心线程 8 最大线程 20 保活时间30s 存储队列 10 有守护线程 拒绝策略:将超负荷任务回退到调用者
*/
private static ExecutorService executor = new ThreadPoolExecutor(8, 20,
30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10),
new ThreadFactoryBuilder().setNameFormat("User_Async_FutureTask-%d").setDaemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy());
@SuppressWarnings("all")
public LingluoUserBehaviorDataDTO getUserAggregatedResult(final Long userId) {
log.info("LingluoFutureTask的线程:" + Thread.currentThread());
long unPayCount = 0, unDeliveryCount = 0, unReceiveCount = 0,
evaluateCount = 0, refundCount = 0;
unPayCount = userService.unPayCountByUserId(userId);
unDeliveryCount = userService.unPayCountByUserId(userId);
unReceiveCount = userService.unPayCountByUserId(userId);
evaluateCount = userService.unPayCountByUserId(userId);
refundCount = userService.unPayCountByUserId(userId);
//使用Builder建造者模式返回结果
LingluoUserBehaviorDataDTO userBehaviorData =
LingluoUserBehaviorDataDTO.builder().unPayCount(unPayCount).unDeliveryCount(unDeliveryCount)
.unReceiveCount(unReceiveCount).evaluateCount(evaluateCount)
.refundCount(refundCount).build();
return userBehaviorData;
}
}
我们启动项目:开启调用 http://localhost:8080/user/get/data?userId=1
当我们线程池配置为:核心线程 8 最大线程 20 保活时间30s 存储队列 10 的时候,我们测试的结果如下:
三.多线程并发执行任务,取结果归集
改写LingluoFutureTask
package com.example.demo.util;
import com.example.demo.entity.LingluoUserBehaviorDataDTO;
import com.example.demo.service.LingluoUserService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.*;
/**
* Created by Lingluo on 2020/1/28.
*/
@Slf4j
@Component
public class LingluoFutureTask {
@Resource
LingluoUserService userService;
/**
* 核心线程 8 最大线程 20 保活时间30s 存储队列 10 有守护线程 拒绝策略:将超负荷任务回退到调用者
*/
private static ExecutorService executor = new ThreadPoolExecutor(8, 20,
30L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10),
new ThreadFactoryBuilder().setNameFormat("User_Async_FutureTask-%d").setDaemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy());
@SuppressWarnings("all")
public LingluoUserBehaviorDataDTO getUserAggregatedResult(final Long userId) {
log.info("MyFutureTask的线程:" + Thread.currentThread());
long unPayCount = 0, unDeliveryCount = 0, unReceiveCount = 0,
evaluateCount = 0, refundCount = 0;
try {
Future<Long> unPayCountFT = executor.submit(() -> userService.unPayCountByUserId(userId));
Future<Long> unDeliveryCountFT = executor.submit(() -> userService.unDeliveryCountByUserId(userId));
Future<Long> unReceiveCountFT = executor.submit(() -> userService.unReceiveCountByUserId(userId));
Future<Long> evaluateCountFT = executor.submit(() -> userService.evaluateCountByUserId(userId));
Future<Long> refundCountFT = executor.submit(() -> userService.refundCountByUserId(userId));
//get阻塞
unPayCount = unPayCountFT.get();
unDeliveryCount = unDeliveryCountFT.get();
unReceiveCount = unReceiveCountFT.get();
evaluateCount = evaluateCountFT.get();
refundCount = refundCountFT.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
log.error(">>>>>>聚合查询用户聚合信息异常:" + e + "<<<<<<<<<");
}
//使用Builder建造者模式返回结果
LingluoUserBehaviorDataDTO userBehaviorData =
LingluoUserBehaviorDataDTO.builder().unPayCount(unPayCount).unDeliveryCount(unDeliveryCount)
.unReceiveCount(unReceiveCount).evaluateCount(evaluateCount)
.refundCount(refundCount).build();
return userBehaviorData;
}
}
再次测试的结果如下:
可以看到速度快了5倍~
返回的结果集
这个异步计算的Future为什么这么神奇,关注公众号【灵洛的人间乐园】且听下回分解~