前言
一个理论:java 中的线程不被允许被销毁
一个设想:函数式语言中怎么使用设计模式
定时任务处理大数据量数据编码逻辑
定时任务简单处理逻辑
@RestController()
@RequestMapping(value ="task" )
public class TaskController {
@Autowired
ITaskService taskService;
@RequestMapping(value = "/baseTask",method = RequestMethod.GET)
public void baseTask() {
baseTask();
}
@Override
public void baseTask() {
int pageSize = 1000;
Long index = 0L;
List<Long> dataList = List.of();
for (Integer pageNum = 1; pageNum <= 300; pageNum++) {
try {
dataList = getBaseList(index, pageSize);
} catch (Exception e) {
log.error("getBaseList error", e);
}
if (CollectionUtils.isEmpty(dataList)) {
break;
}
baseTaskList(dataList);
//添加定时任务游标
index = dataList.get(dataList.size() - 1);
log.info("定时任务 {} 当前页数 {} 当前页数 {} 推送当前游标 {}", "baseTask", pageNum, dataList.size(), index);
}
}
private void baseTaskList(List<Long> dataList) {
if (CollectionUtils.isEmpty(dataList)) {
return;
}
ExecutorService es = ExecutorServiceUtil.init(8, 8, 1000000, TimeUnit.MILLISECONDS, "baseTask");
for (Long customerId : dataList) {
es.submit(() -> {
try {
RespObj<Long> respObj = baseTaskData(customerId);
log.info("baseTaskData customerId {} 推送结果 success {} respObj {}", customerId, respObj.getSuccess(), respObj.getMsg());
} catch (Exception e) {
log.debug("baseTaskData 异常 customerId {}", customerId, e);
}
});
}
ExecutorServiceUtil.stop(es);
}
private RespObj<Long> baseTaskData(Long customerId) {
RespObj<Long> respObj = new RespObj(customerId, "推送成功");
if (customerId % 10 == 1 || customerId % 10 == 2) {
respObj.setSuccess(false);
respObj.setMsg("错误类型1 尾号为1,2");
return respObj;
}
return respObj;
}
}
线程池工具类第一版
@Slf4j
public class ExecutorServiceUtil {
public static final int TIMEOUT = 60;
public static ExecutorService init(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, String threadPoolName) {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(threadPoolName).setUncaughtExceptionHandler((thread, throwable)-> {log.info("ThreadPool {} got exception",thread,throwable);}).build();
return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, unit,
new LinkedBlockingQueue<>(), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
}
public static void stop(ExecutorService es) {
//执行后不再接收新任务,如果里面有任务,就执行完
es.shutdown();
try {
//通常shutdown之后调用awaitTermination,作用是:后者会阻塞当前线程,等待剩余任务执行完,然后继续往下执行。如果不适用await,那么shutdown之后,很可能导致剩余任务得不到执行(整个程序退出),或是执行出现异常(某些资源被释放之类)。
if (!es.awaitTermination(TIMEOUT, TimeUnit.MINUTES)) {
//执行后不再接受新任务,如果有等待任务,移出队列;有正在执行的,尝试停止 超时的时候向线程池中所有的线程发出中断(interrupted)。
es.shutdownNow();
}
} catch (InterruptedException ignore) {
es.shutdownNow();
}
}
}
此程序可以实现每次对 1000 数据进行批量并发处理,但是缺点很多。
- 缺点 1:每次处理 1000 数据就会创建线程池,因为
shutdown()
方法一旦执行,该线程池就不可用。 - 缺点 2:通用处理逻辑太复杂,要求太多,目前也没有对日志进行统一处理。
优化线程池 CompletableFuture 函数式编程设计模式
定时任务处理逻辑函数式
@RestController()
@RequestMapping(value ="task" )
public class TaskController {
@RequestMapping(value = "/taskTemplate",method = RequestMethod.GET)
public Map<String, Long> taskTemplate() {
return taskService.taskTemplate();
}
@Override
public Map<String, Long> taskTemplate() {
ExecutorServiceTaskUtil execute = ExecutorServiceTaskUtil.execute("taskTemplate", 4,
(ExecutorServiceTaskUtil executorServiceTaskUtil) -> {
int pageSize = 1000;
Long index = 0L;
List<Long> dataList = List.of();
for (Integer pageNum = 1; pageNum <= 100; pageNum++) {
try {
dataList = getBaseList(index, pageSize);
} catch (Exception e) {
log.error("taskTemplate getBaseList error", e);
}
if (CollectionUtils.isEmpty(dataList)) {
break;
}
executorServiceTaskUtil.taskList(dataList.stream().map(
(cid) -> executorServiceTaskUtil.taskData(() -> taskData(cid), cid)).collect(Collectors.toList()));
//添加定时任务游标
index = dataList.get(dataList.size() - 1);
log.info("定时任务 {} 当前页数 {} 当前页数 {} 推送当前游标 {} listResult {}", "taskTemplate", pageNum,
dataList.size(), index, JSONUtil.toJsonStr(executorServiceTaskUtil.getTaskListCountMap()));
}
});
return execute.getTaskCountMap();
}
private TaskResultResponse taskData(Long customerId) {
TaskResultResponse<Long> result = new TaskResultResponse(customerId, true);
if (customerId % 10 == 1 || customerId % 10 == 2) {
result.setMsg("错误类型1 尾号为1,2");
return result;
}
return result;
};
}
定时任务统一返回值
@Data
public class TaskResultResponse<T> {
private T data;
private Boolean success;
private String msg;
public TaskResultResponse(T t, Boolean success) {
this.success = success;
if (success) {
this.msg = "执行成功";
} else {
this.msg = "执行失败";
}
this.data = t;
}
}
定时任务工具类第二版
@Slf4j
@Data
public class ExecutorServiceTaskUtil {
//核心线程数
private Integer poolSize;
//线程名
private String threadPoolName;
//线程池
private ThreadPoolExecutor taskExecutor;
//任务情况统计
Map<String, Long> taskCountMap = Maps.newConcurrentMap();
//任务列表统计
Map<String, Long> taskListCountMap = Maps.newConcurrentMap();
//任务总数
private Long taskCount = 0L;
private Long successCount = 0L;
private Long failCount = 0L;
//开始时间
private Long startTime = 0L;
//结束时间
private Long endTime = 0L;
public ExecutorServiceTaskUtil(Integer poolSize, String threadPoolName) {
this.poolSize = poolSize;
this.threadPoolName = threadPoolName;
//定义线程池名 和 线程 抛出异常处理机制
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("timedTask-" +threadPoolName + "-%d").
setUncaughtExceptionHandler((thread, throwable)-> log.error("ThreadPool {} error",thread,throwable)).build();
//定义全为核心线程,因为没有救急线程 long keepAliveTime, TimeUnit unit, 参数无意义
//救急线程在定时的线程池的队列满的时候且核心线程都在执行时 才会创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(poolSize, poolSize,
1000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
this.taskExecutor = executor;
this.startTime = System.currentTimeMillis();
}
//Consumer 接口 做了处理器
public static ExecutorServiceTaskUtil execute(String taskName,int poolSize,Consumer<ExecutorServiceTaskUtil> consumer){
ExecutorServiceTaskUtil executorServiceTaskUtil = new ExecutorServiceTaskUtil(poolSize,taskName);
try {
consumer.accept(executorServiceTaskUtil);
} finally {
executorServiceTaskUtil.stop();
}
return executorServiceTaskUtil;
}
//supplier接口 做了包装器
public Supplier taskData(Supplier<TaskResultResponse> supplier, Object resultData){
return () -> {
TaskResultResponse<Object> result = new TaskResultResponse<>(resultData, true);
try {
result = supplier.get();
log.info("{} taskData resultData {} result {}", threadPoolName, resultData, JSONUtil.toJsonStr(result));
} catch (Exception e) {
log.error("{} taskData 异常 resultData {}",threadPoolName, resultData, e);
result.setMsg("执行异常");
result.setSuccess(false);
}
return result;
};
}
//supplier接口 做了执行器
public void taskList(List<Supplier> dataSupplierList) {
if(dataSupplierList == null || dataSupplierList.size() == 0){
return;
}
int size = dataSupplierList.size();
long startTime = System.currentTimeMillis();
CompletableFuture<TaskResultResponse<Object>>[] callableArray = new CompletableFuture[size];
for (int i = 0; i < size; i++) {
callableArray[i] = CompletableFuture.supplyAsync(dataSupplierList.get(i), taskExecutor);
}
try {
CompletableFuture.allOf(callableArray);
} catch (Exception e) {
log.error("{} CompletableFuture allOf error",threadPoolName ,e);
}
List<CompletableFuture<TaskResultResponse<Object>>> callableList = Lists.newArrayList(callableArray);
this.taskListCountMap = callableList.stream()
.collect(Collectors.groupingBy(e -> {
try {
return e.get().getMsg();
} catch (Exception ex) {
return "异步执行失败";
}
}, Collectors.counting()));
Map<Boolean, Long> collect = callableList.stream()
.collect(Collectors.groupingBy(e -> {
try {
return e.get().getSuccess();
} catch (Exception ex) {
return false;
}
}, Collectors.counting()));
addListCount();
Long falseListCount = collect.getOrDefault(false, 0L);
Long successListCount = collect.getOrDefault(true, 0L);
taskCount +=size;
successCount += successListCount;
failCount += falseListCount;
log.info("{} taskList time {}s" , threadPoolName , (System.currentTimeMillis() - startTime)/1000 );
}
private void addListCount(){
if(taskListCountMap == null ||taskListCountMap.size() == 0){
return ;
}
for (String key : taskListCountMap.keySet()) {
Long oldValue = taskCountMap.get(key);
// 键不存在,将键值对添加到 ConcurrentHashMap 中
if (oldValue == null) {
taskCountMap.putIfAbsent(key, 0L);
oldValue = 0L;
}
Long value = taskListCountMap.get(key);
// 键存在,将键对应的值自增 1
while (true) {
Long newValue = oldValue + value;
if (taskCountMap.replace(key, oldValue, newValue)) {
// 替换成功,退出循环
break;
} else {
// 替换失败,重新获取最新值并重试
oldValue = taskCountMap.get(key);
}
}
}
}
public void stop() {
//执行后不再接收新任务,如果里面有任务,就执行完
taskExecutor.shutdown();
try {
//通常shutdown之后调用awaitTermination,作用是:后者会阻塞当前线程,等待剩余任务执行完,然后继续往下执行。如果不适用await,那么shutdown之后,很可能导致剩余任务得不到执行(整个程序退出),或是执行出现异常(某些资源被释放之类)。
if (!taskExecutor.awaitTermination(1000, TimeUnit.MINUTES)) {
//执行后不再接受新任务,如果有等待任务,移出队列;有正在执行的,尝试停止 超时的时候向线程池中所有的线程发出中断(interrupted)。
taskExecutor.shutdownNow();
}
} catch (InterruptedException ignore) {
taskExecutor.shutdownNow();
}
this.endTime = System.currentTimeMillis();
log.info("{} time {}s" , threadPoolName , (startTime - endTime)/1000 );
}
}
使用了 CompletableFutureApi 处理线程任务,通过函数式接口处理通用代码,通用日志。
- 优点 1:创建一次线程池,可重复使用此线程池
- 优点 2:通用处理逻辑简化,不需要知道工具类中的函数时接口中的定义和逻辑,只需要使用即可,把对应参数传进去工具类进行日志打印和参数的传递
代码解读
为什么要这样写代码?
ExecutorServiceTaskUtil execute = ExecutorServiceTaskUtil.execute("taskTemplate", 4,
(ExecutorServiceTaskUtil executorServiceTaskUtil) -> {
//定时任务处理逻辑
executorServiceTaskUtil.taskList(
dataList.stream().map((cid) -> executorServiceTaskUtil.taskData(
() -> 单个数据处理逻辑, cid)
).collect(Collectors.toList()));
})
定时任务工具类在代码中的集成为以上逻辑
- 定时任务执行:
public static ExecutorServiceTaskUtil execute(String taskName,int poolSize,Consumer<ExecutorServiceTaskUtil> consumer)
- 不希望使用者定义太多线程池的参数,只需定义线程池的名字和线程数。
- 封闭对线程池的操作。
- 其次在定时任务处理逻辑中,会用到线程池工具类对象 通过
interface Consumer<T>
参数接口 进行初始化线程池并且传递到接口的实现方法中,实现对于线程池的初始化和销毁。- 装饰器模式
- 处理器模式
- 数据集合处理
public void taskList(List<Supplier> dataSupplierList)
目的进行任务处理,数据统计。CompletableFuture.supplyAsync(dataSupplierList.get(i), taskExecutor)
用于线程任务处理CompletableFuture.allOf(callableArray);
用于主线程阻塞- 进行数据统计
- 数据处理
public Supplier taskData(Supplier<TaskResultResponse> supplier, Object resultData)
- 进行日志打印
- 必须返回 TaskResultResponse 对象,目的用于数据统计。
- 通过
interface Supplier<T>
参数接口 进行方法传递。- 静态代理模式
- 装饰器模式
展望:之后的函数式接口结合不同的设计模式处理接口
救急线程
注:实验了使用救急线程的情况,救急线程只能在核心线程都在处理中且线程队列满的时候创建 所以不使用救急线程满足需求
ThreadPoolExecutorWrap executor = new ThreadPoolExecutorWrap(0, 10, 1000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), "timedTask-"+threadPoolName+"-");
处理 1000 条数时由于线程队列不满所以创建了一个核心线程处理数据,未创建救急线程