前言
1、JUC是指有关 java.util.concurrent包以及其子包,这些包都是有关线程操作的包
2、HTTPS服务请求中,WEB服务只负责创建主线程来接收外部的HTTPS请求,如果不做任何处理,默认业务逻辑是通过主线程来做的,如果业务执行时间较长且用户访问量较大的情况,WEB服务在单位时间内所能处理的用户请求就会有限,JUC并发编程的核心就是如何来释放主线成以及通过子线程来批量执行任务
3、JUC并发编程并不能提高执行任务所耗费的时间,但是可以极大的提高WEB容器的吞吐量,能在单位时间内接收更多的用户请求
案例(模拟下单)
- 公共代码(创建订单)
@Service
public class OrderTaskService {
public JSONObject create() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("orderId", "order-1101");
System.out.println("OrderTaskService子线程:" + Thread.currentThread().getName());
return jsonObject;
}
}
- 公共代码(操作积分)
@Service
public class ItegralFutureTaskService {
public String operate() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("ItegralFutureTaskService子线程:" + Thread.currentThread().getName());
return "休眠500毫秒后返回";
}
}
- 公共代码(操作库存)
@Service
public class StockFutureTaskService {
public String operate() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("StockFutureTaskService子线程:" + Thread.currentThread().getName());
return "休眠500毫秒后返回";
}
}
同步处理
@GetMapping("createOrderCommon")
public JSONObject createOrder() {
long start = System.currentTimeMillis();
// 本地服务 - 创建订单
String order = orderTaskService.create();
// 三方服务 - 处理库存
String stock = stockFutureTaskService.operate();
// 三方服务 - 处理积分
String itegral = itegralFutureTaskService.operate();
long end = System.currentTimeMillis();
JSONObject orderInfo = new JSONObject();
orderInfo.put("order", order);
orderInfo.put("stock", stock);
orderInfo.put("itegral", itegral);
orderInfo.put("耗时", (end - start));
return orderInfo;
}
{
"itegral": "积分处理:http-nio-0.0.0.0-8801-exec-2 休眠500毫秒后返回",
"stock": "库存处理:http-nio-0.0.0.0-8801-exec-2 休眠500毫秒后返回",
"order": "订单处理:http-nio-0.0.0.0-8801-exec-2"
"耗时": 1014,
}
分析:模拟环境下,积分处理花费500毫秒,库存处理花费500毫米,再加上订单处理耗时应该在:1000+毫秒,最终WEB容器主线程被占用的时间为1014毫秒
异步处理(FutureTask)
@GetMapping("createOrderFutureTask")
public JSONObject createOrderFutureTask() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
JSONObject orderInfo = new JSONObject();
// 本地服务 - 创建订单
String order = orderTaskService.create();
orderInfo.put("order", order);
// 三方服务 - 处理库存 TODO【异步】
Callable<String> callableForStock = new Callable<String>() {
@Override
public String call() throws Exception {
return stockFutureTaskService.operate();
}
};
// 三方服务 - 处理积分 TODO【异步】
Callable<String> callableForItegral = new Callable<String>() {
@Override
public String call() throws Exception {
return itegralFutureTaskService.operate();
}
};
FutureTask<String> futureForStock = new FutureTask<>(callableForStock);
FutureTask<String> futureForItegral = new FutureTask<>(callableForItegral);
new Thread(futureForStock).start();
new Thread(futureForItegral).start();
orderInfo.put("stock", futureForStock.get()); // TODO 阻塞式获取执行结果,所以会占用web容器的主线程
orderInfo.put("itegral", futureForItegral.get()); // TODO 阻塞式获取执行结果,所以会占用web容器的主线程
long end = System.currentTimeMillis();
orderInfo.put("耗时", (end - start));
System.out.println("任务耗时:" + orderInfo.get("耗时"));
return orderInfo;
}
{
"itegral": "积分处理:Thread-23 休眠500毫秒后返回",
"stock": "库存处理:Thread-22 休眠500毫秒后返回",
"order": "订单处理:http-nio-0.0.0.0-8801-exec-9"
"耗时": 508,
}
分析1:模拟环境下,积分处理花费500毫秒,库存处理花费500毫米,但积分处理和库存处理是并发执行的,再加上订单处理耗时应该在:500+毫秒,最终WEB容器主线程被占用的时间为508毫秒
分析2:通过FutureTask实现多线程并发处理时,如果WEB容器主线程fork出来的子线程没有返回执行结果,那么主线程是会一直阻塞,直到子线程返回结果
优化FutureTask
Asynchronous Requests
DeferredResult和Callable都是为了异步生成返回值提供基本的支持。一个请求进来,在没有得到返回数据之前,DispatcherServlet和所有Filter就会退出Servlet容器线程,但响应保持打开状态,一旦返回数据有了,DispatcherServlet就会被再次调用并且处理,以异步产生的方式,向请求端返回值。这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量
使用Callable对FutureTask优化
@GetMapping("createOrderCallable")
public Callable<JSONObject> createOrderCallable() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
Callable<JSONObject> callable = new Callable<JSONObject>() {
@Override
public JSONObject call() throws Exception {
return createOrderFutureTask();
}
};
long end = System.currentTimeMillis();
System.out.println("主线程处理耗时:" + Thread.currentThread().getName() + " " +(end - start) + "毫秒");
return callable;
}
主线程处理耗时:http-nio-0.0.0.0-8801-exec-1 0毫秒
OrderTaskService子线程:task-1
ItegralFutureTaskService子线程:Thread-15
StockFutureTaskService子线程:Thread-14
任务耗时:540
分析1:从运行日志上可以看出来,任务执行花费了540毫秒,WEB容器的主线称只被占用了0毫秒
异步处理(CompletableFuture)
- 自定义线程池对象并纳入容器管理
/**
* @描述: TODO 自定义线程池来处理异步任务。在方法上添加:@Async("api-asyn-taskExecutor")
* @作者: lixing lixing_java@163.com
* @日期 2020/2/19 21:18
*/
@Component
@Configuration
public class ThreadPoolTaskExecutorConfig {
@Bean("api-asyn-taskExecutor")
public Executor taskExecutorPool() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("自定义异步线程池"); // 线程名字前缀
threadPoolTaskExecutor.setCorePoolSize(5); // 核心线程数
threadPoolTaskExecutor.setQueueCapacity(10); // 任务队列容量(缓存队列)
threadPoolTaskExecutor.setMaxPoolSize(10); //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
threadPoolTaskExecutor.setKeepAliveSeconds(120); // 允许空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
threadPoolTaskExecutor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy() ); // 任务拒绝策略
// 关闭线程池时是否等待当前调度任务完成后才开始关闭
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown( true ); // 等待
threadPoolTaskExecutor.setAwaitTerminationSeconds( 60 ); // 等待时长
threadPoolTaskExecutor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 异常统一处理......发送邮件、发送短信
}
});
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}
- 实现代码
/**
* 创建订单:JAVA8新特性
*/
@Resource
private ThreadPoolTaskExecutorConfig threadPoolTaskExecutorConfig;
@GetMapping("createOrderCompletableFuture")
public JSONObject createOrderCompletableFuture() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
CompletableFuture<JSONObject> supplyAsync = CompletableFuture.supplyAsync(() -> {
return createOrder();
}, threadPoolTaskExecutorConfig.taskExecutorPool());
long end = System.currentTimeMillis();
System.out.println("主线程处理耗时:" + Thread.currentThread().getName() + " " +(end - start) + "毫秒");
return supplyAsync.get();
}