多任务执行时,共享请求对象被并发修改

导出数据缺失问题分析

共享的请求对象被并发修改

baseRequest 是共享对象,多个线程同时修改其 page 属性会导致数据错乱:

源代码如下

java 复制代码
    /**
	 * 并发处理数据
	 */
	private void processBatches(StudentPageRequest baseRequest,
										 int totalPages, ExcelWriter excelWriter,
										 WriteSheet writeSheet) {
		if (totalPages <= 1) {
			return;
		}
		List<CompletableFuture<Void>> futures = new ArrayList<>();
		// 每页单独一个任务执行
		for (int pageNum = 1; pageNum <= totalPages; pageNum++) {
			final int currentPage = pageNum;
			// 多任务执行
			CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
				try {
					// 设置当前页
					baseRequest.setPage(currentPage);
					PageResponse<List<StudentResponse>> pageData = this.queryPage(baseRequest);
					// 并行处理并组装数据
					List<List<String>> rowData = parallelConvertToRowData(pageData.getData());
					synchronized (excelWriter) {
						excelWriter.write(rowData, writeSheet);
					}
					log.info("已完成 {}/{} 页,数据量:{}", currentPage, totalPages, pageData.getData().size());
				} catch (Exception e) {
					throw new AppRuntimeException(ResponseCode.EXPORT_FILE_FAILED, e);
				}
			}, importTaskExecutor);
			futures.add(future);
		}

		// 等待所有任务完成,30分钟超时
		CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
				.orTimeout(30, TimeUnit.MINUTES)
				.join();
	}

修改方案

方案1:为每个任务创建独立的请求对象(推荐)

java 复制代码
    /**
	 * 并发处理数据
	 */
	private void processBatches(StudentPageRequest baseRequest,
								int totalPages, ExcelWriter excelWriter,
								WriteSheet writeSheet) {
		if (totalPages <= 1) {
			return;
		}
		List<CompletableFuture<Void>> futures = new ArrayList<>();
		// 每页单独一个任务执行
		for (int pageNum = 1; pageNum <= totalPages; pageNum++) {
			final int currentPage = pageNum;
			// 为每个任务创建独立的请求对象,拷贝属性
			StudentPageRequest pageRequest = createStudentPageRequest(baseRequest);
			pageRequest.setPage(currentPage);
			// 多任务执行
			CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
				try {
					PageResponse<List<StudentResponse>> pageData = this.queryPage(pageRequest);
					// 并行处理并组装数据
					List<List<String>> rowData = parallelConvertToRowData(pageData.getData());
					synchronized (excelWriter) {
						excelWriter.write(rowData, writeSheet);
					}
					log.info("已完成 {}/{} 页,数据量:{}", currentPage, totalPages, pageData.getData().size());
				} catch (Exception e) {
					throw new AppRuntimeException(ResponseCode.EXPORT_FILE_FAILED, e);
				}
			}, importTaskExecutor);
			futures.add(future);
		}

		// 等待所有任务完成,30分钟超时
		CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
				.orTimeout(30, TimeUnit.MINUTES)
				.join();
	}

方案2:使用线程局部变量

java 复制代码
    /**
	 * 并发处理数据
	 */
	private void processBatches(StudentPageRequest baseRequest,
								int totalPages, ExcelWriter excelWriter,
								WriteSheet writeSheet) {
		if (totalPages <= 1) {
			return;
		}
		List<CompletableFuture<Void>> futures = new ArrayList<>();
		// 每页单独一个任务执行
		for (int pageNum = 1; pageNum <= totalPages; pageNum++) {
			final int currentPage = pageNum;

			// 多任务执行
			CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
				try {
					// 使用线程局部变量,拷贝属性
					StudentPageRequest pageRequest = createStudentPageRequest(baseRequest);
					pageRequest.setPage(currentPage);
					PageResponse<List<StudentResponse>> pageData = this.queryPage(pageRequest);
					// 并行处理并组装数据
					List<List<String>> rowData = parallelConvertToRowData(pageData.getData());
					synchronized (excelWriter) {
						excelWriter.write(rowData, writeSheet);
					}
					log.info("已完成 {}/{} 页,数据量:{}", currentPage, totalPages, pageData.getData().size());
				} catch (Exception e) {
					throw new AppRuntimeException(ResponseCode.EXPORT_FILE_FAILED, e);
				}
			}, importTaskExecutor);
			futures.add(future);
		}

		// 等待所有任务完成,30分钟超时
		CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
				.orTimeout(30, TimeUnit.MINUTES)
				.join();
	}

总结:

在多线程并发场景下,避免使用共享的对象,建议为每个人任务创建独立的对象。

相关推荐
腻害兔9 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社10 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
萧瑟余晖11 小时前
JDK 26 新特性详解
java·开发语言
马优晨11 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
维天说14 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane199415 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
噢,我明白了15 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
吠品16 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘16 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记16 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端