欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习
1 代码实例
1.1 A服务
声明A服务提供五个方法:
public class BizParamDTO {
private String field;
}
public interface AService {
public String a1(BizParamDTO param) throws Exception;
public String a2(BizParamDTO param) throws Exception;
public String a3(BizParamDTO param) throws Exception;
public String a4(BizParamDTO param) throws Exception;
public String a5(BizParamDTO param) throws Exception;
}
a1-a4休眠100毫秒,a5休眠600毫秒:
@Service
public class AServiceImpl implements AService {
@Override
public String a1(BizParamDTO param) throws Exception {
System.out.println(Thread.currentThread().getName() + ",a1 param=" + param);
TimeUnit.MILLISECONDS.sleep(100);
return param.getField();
}
@Override
public String a2(BizParamDTO param) throws Exception {
System.out.println(Thread.currentThread().getName() + ",a2 param=" + param);
TimeUnit.MILLISECONDS.sleep(100);
return param.getField();
}
@Override
public String a3(BizParamDTO param) throws Exception {
System.out.println(Thread.currentThread().getName() + ",a3 param=" + param);
TimeUnit.MILLISECONDS.sleep(100);
return param.getField();
}
@Override
public String a4(BizParamDTO param) throws Exception {
System.out.println(Thread.currentThread().getName() + ",a4 param=" + param);
TimeUnit.MILLISECONDS.sleep(100);
return param.getField();
}
@Override
public String a5(BizParamDTO param) throws Exception {
System.out.println(Thread.currentThread().getName() + ",a5 param=" + param);
TimeUnit.MILLISECONDS.sleep(600);
return param.getField();
}
}
1.2 B服务
B服务同步调用A服务五个方法:
public interface BService {
public void b(BizParamDTO param) throws Exception;
}
@Service
public class BServiceImpl implements BService {
@Resource
private AService aservice;
@Override
public void b(BizParamDTO param) throws Exception {
StopWatch stopWatch = new StopWatch();
stopWatch.start("taskB");
String r1 = aservice.a1(param);
String r2 = aservice.a2(param);
String r3 = aservice.a3(param);
String r4 = aservice.a4(param);
String r5 = aservice.a5(param);
List<String> result = Arrays.asList(r1, r2, r3, r4, r5);
stopWatch.stop();
System.out.println("b1-costTime=" + stopWatch.getTotalTimeMillis() + "ms");
}
}
1.3 C服务
C服务通过异步调用A服务:
public interface CService {
public void c(BizParamDTO param) throws Exception;
}
@Service
public class CServiceImpl implements CService {
private final static Integer TYPE = TypeEnum.CPU.getCode();
@Resource
private AService aservice;
@Override
public void c(BizParamDTO param) throws Exception {
StopWatch stopWatch = new StopWatch();
stopWatch.start("taskC");
Future<String> f1 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
@Override
public String call() throws Exception {
StopWatch s = new StopWatch();
s.start();
String result = aservice.a1(param);
s.stop();
System.out.println("a1-costTime=" + s.getTotalTimeMillis() + "ms");
return result;
}
});
Future<String> f2 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
@Override
public String call() throws Exception {
StopWatch s = new StopWatch();
s.start();
String result = aservice.a2(param);
s.stop();
System.out.println("a2-costTime=" + s.getTotalTimeMillis() + "ms");
return result;
}
});
Future<String> f3 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
@Override
public String call() throws Exception {
StopWatch s = new StopWatch();
s.start();
String result = aservice.a3(param);
s.stop();
System.out.println("a3-costTime=" + s.getTotalTimeMillis() + "ms");
return result;
}
});
Future<String> f4 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
@Override
public String call() throws Exception {
StopWatch s = new StopWatch();
s.start();
String result = aservice.a4(param);
s.stop();
System.out.println("a4-costTime=" + s.getTotalTimeMillis() + "ms");
return result;
}
});
Future<String> f5 = MyThreadFactory.get(TYPE).submit(new Callable<String>() {
@Override
public String call() throws Exception {
StopWatch s = new StopWatch();
s.start();
String result = aservice.a5(param);
s.stop();
System.out.println("a5-costTime=" + s.getTotalTimeMillis() + "ms");
return result;
}
});
// 等待结果
StopWatch watch = new StopWatch("waitWatch");
watch.start("f1.get()");
String r1 = f1.get();
watch.stop();
watch.start("f2.get()");
String r2 = f2.get();
watch.stop();
watch.start("f3.get()");
String r3 = f3.get();
watch.stop();
watch.start("f4.get()");
String r4 = f4.get();
watch.stop();
watch.start("f5.get()");
String r5 = f5.get();
watch.stop();
// 输出结果
List<String> result = Arrays.asList(r1, r2, r3, r4, r5);
stopWatch.stop();
System.out.println("c1-costTime=" + stopWatch.getTotalTimeMillis() + "ms,costTimeDetail=" + watch.prettyPrint());
}
}
1.4 线程池
我们把线程池划分为两种类型:
public enum TypeEnum {
IO(1, "IO密集"),
CPU(2, "CPU密集")
}
CPU密集型线程数:CPU数量+1
IO密集型线程数:CPU数量除以(1-阻塞系数0.9)
public class MyThreadFactory {
/** 线程执行器 **/
private static volatile ThreadPoolExecutor executor;
/** 队列存放任务数 **/
private static int QUEUE_MAX_SIZE = 1000;
/** 线程存活时间 **/
private static long KEEP_ALIVE_TIME = 1000;
public static ThreadPoolExecutor get(int type) {
if (executor == null) {
synchronized (ThreadFactory.class) {
if (executor == null) {
int cpuNum = Runtime.getRuntime().availableProcessors();
int coreSize = cpuNum;
if (type == TypeEnum.CPU.getCode()) {
coreSize = cpuNum + 1;
} else if (type == TypeEnum.IO.getCode()) {
coreSize = cpuNum * 10;
}
int maxSize = coreSize;
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(QUEUE_MAX_SIZE);
executor = new ThreadPoolExecutor(coreSize, maxSize, KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, queue);
}
}
}
return executor;
}
/**
* 本机8核16处理器
*/
public static void main(String[] args) {
int cpuNum = Runtime.getRuntime().availableProcessors(); // 16
System.out.println("cpuNum=" + cpuNum);
}
}
1.5 访问端点
@RestController
@RequestMapping("/test")
public class BizController {
@Resource
private BService bservice;
@Resource
private CService cservice;
@PostMapping("/biz1")
public boolean biz1(@RequestBody BizParamDTO param) throws Exception {
bservice.b(param);
return true;
}
@PostMapping("/biz2")
public boolean biz2(@RequestBody BizParamDTO param) throws Exception {
cservice.c(param);
return true;
}
}
2 单次执行
2.1 同步执行
postman访问端点biz1,此时选择CPU密集型线程池:
http://localhost:8080/javafront/test/biz1
{
"field": "a"
}
耗时日志如下:
b1-costTime=1036ms
耗时计算公式:
100ms(a1) + 100ms(a2) + 100ms(a3) + 100ms(a4) + 500ms(a5) = 1000ms
2.2 异步执行
postman访问端点biz2:
http://localhost:8080/javafront/test/biz2
{
"field": "a"
}
耗时日志如下:
a2-costTime=104ms
a4-costTime=104ms
a1-costTime=104ms
a3-costTime=104ms
a5-costTime=602ms
c1-costTime=604ms,costTimeDetail=StopWatch 'waitWatch': running time = 604224000 ns
---------------------------------------------
ns % Task name
---------------------------------------------
105678000 017% f1.get()
000001800 000% f2.get()
000048100 000% f3.get()
000000400 000% f4.get()
498495700 083% f5.get()
本次耗时日志稍显复杂,可以把日志分为执行部分和等待部分:
- 执行部分
- a1-a4分别执行耗时104ms
- a5执行耗时602ms
a2-costTime=104ms
a4-costTime=104ms
a1-costTime=104ms
a3-costTime=104ms
a5-costTime=602ms
- 等待部分
- c1总共耗时604ms
- f1等待时间105ms
- f2-f4等待时间为纳秒级
- f5等待时间498ms
c1-costTime=604ms,costTimeDetail=StopWatch 'waitWatch': running time = 604224000 ns
---------------------------------------------
ns % Task name
---------------------------------------------
105678000 017% f1.get()
000001800 000% f2.get()
000048100 000% f3.get()
000000400 000% f4.get()
498495700 083% f5.get()
2.3 本章总结
- 同步执行耗时为每个节点耗时累加
- 异步执行耗时为节点中耗时最长节点
- 单次执行耗时异步优于同步
3 压力测试
3.1 压测思路
- 使用工具JMeter
- 线程组配置
- 线程数10、20、30递增
- Ramp-Up时间0秒
- 持续时间60s
- 循环次数永久
- 关注聚合报告指标
- 95Line
- 吞吐量
- 异常比例
- 执行方式
- 同步执行
- 异步执行,线程池使用CPU密集型
- 异步执行,线程池使用IO密集型
3.2 压测分析
3.2.1 压测结果
- 同步执行
- 在不同线程数下耗时总体稳定,均为1000ms左右
- 异步(IO密集型)
- 在不同线程数下耗时总体稳定,均为600ms左右
- 异步(CPU密集型)
- 随着线程数增多,耗时越来越大,性能表现不如同步
3.2.2 耗时分析
现在分析异步在50线程时耗时日志,分析耗时主要发生执行部分,还是发生在等待部分。
(1) 异步(IO密集型)
a5-costTime=601ms
a1-costTime=108ms
a4-costTime=108ms
a2-costTime=108ms
a3-costTime=108ms
c1-costTime=602ms,costTimeDetail=StopWatch 'waitWatch': running time = 602183001 ns
---------------------------------------------
ns % Task name
---------------------------------------------
105515000 018% f1.get()
000000200 000% f2.get()
000777201 000% f3.get()
000101800 000% f4.get()
495788800 082% f5.get()
- 执行部分
- a1-a4分别执行耗时约为108ms
- a5执行耗时601ms
- 等待部分
- f1等待时间约为150ms
- f5等待时间约为495ms
- 分析小结
- 执行和等待均符合预期
(2) 异步(CPU密集型)
a1-costTime=110ms
a2-costTime=109ms
a3-costTime=110ms
a5-costTime=613ms
a4-costTime=110ms
c1-costTime=3080ms,costTimeDetail=StopWatch 'waitWatch': running time = 3080277201 ns
---------------------------------------------
ns % Task name
---------------------------------------------
2528728000 082% f1.get()
016059001 001% f2.get()
031992801 001% f3.get()
000046199 000% f4.get()
503451200 016% f5.get()
-
执行部分
- a1-a4分别执行耗时约为110ms
- a5执行耗时613ms
-
等待部分
- f1等待时间约为2.5s
- f5等待时间约为500ms
- 其它任务等待时间也有所增加
-
分析小结
- 耗时主要在等待部分
- 执行部分耗时符合预期
3.3 压测总结
- 如果线程池选择不合适,异步性能不如同步
- 如果任务耗时长,应该增加配置线程数
4 文章总结
本文第一编写了同步和异步代码,并在代码中输出了耗时日志。第二分析单次执行同步和异步的表现,异步优于同步。第三结合不同线程池配置进行压测,如果线程池选择不合适,异步执行性能不如同步,所以要配置合适线程数。
欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习