一种执行异步任务的设计

先上代码,使用了 lombok 库简化了一些样板代码。

@Accessors(chain = true)
@Data
@RequiredArgsConstructor(staticName = "of")
public class BaseRspDTO<T extends Object> {
    // 区分是 DTO 返回的唯一标记,比如是 UserInfoDTO 还是 BannerDTO
    @NonNull private String key;
    // 返回的 data
    private T data;
}

/**
 * 执行异步任务
 */
public class Task {
    public static List<BaseRspDTO<Object>> execute(List<Callable<BaseRspDTO<Object>>> taskList, long timeOut, ExecutorService executor) {
        List<BaseRspDTO<Object>> resultList = new ArrayList<>();
        // 校验参数
        if (taskList == null || taskList.size() == 0 || executor == null || timeOut <= 0) {
            return resultList;
        }

        // 提交任务
        CompletionService<BaseRspDTO<Object>> baseDTOCompletionService = new ExecutorCompletionService<BaseRspDTO<Object>>(executor);
        for (Callable<BaseRspDTO<Object>> task : taskList) {
            baseDTOCompletionService.submit(task);
        }

        try {
            // 遍历获取结果
            for (int i = 0; i < taskList.size(); i++) {
                Future<BaseRspDTO<Object>> baseRspDTOFuture = baseDTOCompletionService.poll(timeOut, TimeUnit.SECONDS);
                resultList.add(baseRspDTOFuture.get());
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        return resultList;
    }
}


private void featureMethod(HttpServletRequest request, UserQO qo, User user) {
	//统计 UA
	Callable<BaseRspDTO<Object>> uaTask = () -> {
		healthService.addUserIdToRedisOnlineUserCountStatistics(qo.getHead().getCurrentUserId());
		return BaseRspDTO.of("ua");
	};
	
	// 如果token有效,刷新token
	Callable<BaseRspDTO<Object>> authCodeTask = () -> {
		String authCode = request.getParameter("authCode");
		if (authCode != null) {
			userService.tokenReFlush(authCode);
		}
		return BaseRspDTO.of("authCode").setData(authCode);
	};
	
	ExecutorService executor = Executors.newFixedThreadPool(10);
	List<BaseRspDTO<Object>> resultList = Task.execute(List.of(uaTask, authCodeTask), 3, executor);
	if (resultList.size() == 0) {
		return;
	}
	
	resultList.forEach(result -> {
		if (StringUtils.equals("authCode", result.getKey()) && null != result.getData()) {
			user.setAuthCode((String) result.getData());
		}
	});
}

设计思路

BaseRspDTO 用于承载不同异步任务的返回值,使用 key 作为标识,方便类型强转

Task 类是一个工具类,用于执行多个异步任务

Callable<BaseRspDTO<Object>> uaTask = () -> {
    healthService.addUserIdToRedisOnlineUserCountStatistics(qo.getHead().getCurrentUserId());
    return BaseRspDTO.of("ua");
};

先将原有串行方法进行改造,将其放入一个 Callable 中,并返回 BaseRspDTO,并定义 key。

ExecutorService executor = Executors.newFixedThreadPool(10);
List<BaseRspDTO<Object>> resultList = Task.execute(List.of(uaTask, authCodeTask), 3, executor);

定义线程池,使用 Task.execute 进行异步任务的编排和调用

resultList.forEach(result -> {
    if (StringUtils.equals("authCode", result.getKey()) && null != result.getData()) {
        user.setAuthCode((String) result.getData());
    }
});

最后循环所有的结果,根据 key 来获取返回值。

后续可供优化点

1.key 可以使用枚举
2.样板代码还是很多
3.可以考虑使用策略模式对结果进行解耦,消除多个 if 语句
4.线程池可以使用公用的

posted @ 2022-05-24 10:30  LiuChengloong  阅读(83)  评论(0编辑  收藏  举报