代码参考: https://gitee.com/constfafa/data_structure_and_algorithm/tree/master/java-concurrency
package wangwenjun.phase3.executor.completablefuture.demo02
背景:
spring boot web项目
controller要调用同一个service下的4个服务
目前采用的是串行方式
代码参考:wangwenjun.phase3.executor.completablefuture.demo02.AppV1Test#test
String items = esService.searchQueryItems("xxx");
String guides = esService.searchQueryGuides("xxx");
String applications = esService.getApplications("xxx");
String appoints = esService.getAppointItems("xxx");
串行执行很耗时,如何用多线程来优化呢?
方法1:单例ThreadpoolExecutor#Submit Callable
代码参考:wangwenjun.phase3.executor.completablefuture.demo02.AppV1Test#test4
Callable<String> itemResult = () -> esService.searchQueryItems(keyword);
Callable<String> guideResult = () -> esService.searchQueryGuides(keyword);
Callable<String> appResult = () -> esService.getApplications(keyword);
Callable<String> appointResult = () -> esService.getAppointItems(keyword);
// 使用线程池#submit方法确实是并发执行了
ThreadPoolExecutor executor = SearchRequestThreadPool.getExecutor();
Future<String> itemFuture = executor.submit(itemResult);
Future<String> guideFuture = executor.submit(guideResult);
Future<String> appFuture = executor.submit(appResult);
Future<String> appointFuture = executor.submit(appointResult);
// 获取结果仍旧是串行的 原因就在于Future没有提供回调的方法
list.add(itemFuture.get(3, TimeUnit.SECONDS));
list.add(guideFuture.get(3, TimeUnit.SECONDS));
list.add(appFuture.get(3, TimeUnit.SECONDS));
list.add(appointFuture.get(3, TimeUnit.SECONDS));
方法2:FutureTask + 单例ThreadpoolExecutor
代码参考:wangwenjun.phase3.executor.completablefuture.demo02.AppV3Test#test
// 创建4个futureTask
// 用4个线程来执行
ThreadPoolExecutor threadPoolExecutor = SearchRequestThreadPool.getExecutor();
threadPoolExecutor.execute(itemsFutureTask);
threadPoolExecutor.execute(guidesFutureTask);
threadPoolExecutor.execute(applicationsFutureTask);
threadPoolExecutor.execute(appointsFutureTask);
// 获取结果 仍旧是串行的
items = itemsFutureTask.get(2, TimeUnit.SECONDS);
guides = guidesFutureTask.get(2, TimeUnit.SECONDS);
applications = applicationsFutureTask.get(2, TimeUnit.SECONDS);
appoints = appointsFutureTask.get(2, TimeUnit.SECONDS);
方法1和2存在的问题:查询是并行的 但是由于futureTask#get是串行的 所以是无法保证整个请求在执行时间内返回
对于方法1和方法2可以进行修改
代码参考:wangwenjun.phase3.executor.completablefuture.demo02.AppV1Test#test5
Callable<String> itemResult = () -> esService.searchQueryItems(keyword);
Callable<String> guideResult = () -> esService.searchQueryGuides(keyword);
Callable<String> appResult = () -> esService.getApplications(keyword);
Callable<String> appointResult = () -> esService.getAppointItems(keyword);
// 使用线程池#submit方法确实是并发执行了
ThreadPoolExecutor executor = SearchRequestThreadPool.getExecutor();
Future<String> itemFuture = executor.submit(itemResult);
Future<String> guideFuture = executor.submit(guideResult);
Future<String> appFuture = executor.submit(appResult);
Future<String> appointFuture = executor.submit(appointResult);
// 启动4个线程并发执行 并且通过join方式
Thread thread01 = new Thread(() -> {
try {
list.add(itemFuture.get(3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}, "my-thread-1");
thread01.start();
Thread thread02 = new Thread(() -> {
try {
list.add(guideFuture.get(3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}, "my-thread-2");
thread02.start();
Thread thread03 = new Thread(() -> {
try {
list.add(appFuture.get(3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}, "my-thread-3");
thread03.start();
Thread thread04 = new Thread(() -> {
try {
list.add(appointFuture.get(3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}, "my-thread-04");
thread04.start();
try {
thread01.join();
thread02.join();
thread03.join();
thread04.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
可以看出来,这样的成本是很高的,每次请求要使用8个线程来执行,线程池中的4个线程用来计算,自己启动的4个线程用来执行add操作。根本原因就是Future不支持回调。
方法3:使用CountDownLatch
代码参考:wangwenjun.phase3.executor.completablefuture.demo02.AppV4Test#test
// 使用全局线程池
ThreadPoolExecutor threadPoolExecutor = SearchRequestThreadPool.getExecutor();
threadPoolExecutor.execute(new QueryThread(countDownLatch, esService, "searchQueryItems",
new Class<?>[]{String.class}, new
Object[]{"changliang"}, list));
threadPoolExecutor.execute(new QueryThread(countDownLatch, esService, "searchQueryGuides",
new Class<?>[]{String.class}, new
Object[]{"changliang"}, list));
threadPoolExecutor.execute(new QueryThread(countDownLatch, esService, "getApplications",
new Class<?>[]{String.class}, new
Object[]{"changliang"}, list));
threadPoolExecutor.execute(new QueryThread(countDownLatch, esService, "getAppointItems",
new Class<?>[]{String.class}, new
Object[]{"changliang"}, list));
/**
* 统一时间控制
*/
countDownLatch.await(3,TimeUnit.SECONDS);
可见这种方式是可行的
方法4:使用CompletableFuture
代码参考:wangwenjun.phase3.executor.completablefuture.demo02.AppV5Test
CompletableFuture<Void> future = CompletableFuture.allOf(xxx);
future.get(5, TimeUnit.SECONDS);
总结来说,方法3和方法4都可以使用