Java多线程应用行为异常,请求排查问题根源
Java多线程任务中文件名与LoadingId不匹配问题分析与解决
问题现象
执行TestExecutor类时,主线程从任务队列中获取(id, fileName)对(格式应为(n, "Vn"),0≤n≤25)并提交给线程池执行,但日志显示文件名与loadingId不匹配,例如出现Thread 13 is assigned loading image V0 - 2,正确对应关系应为V0-0;后续大量任务的loadingId都固定为25,完全偏离预期。
问题根源
核心问题在于lambda表达式对成员变量loadingId的不当捕获:
- 在
run()方法中,主线程每次获取任务后,提交的lambda表达式() -> { processLoadRequest(imageFileName, loadingId); }捕获的是类成员变量loadingId,而非当前任务对应的局部值。 - 主线程执行速度远快于线程池的任务启动速度,在lambda被线程池线程执行前,主线程已经多次更新
loadingId的值,导致lambda最终拿到的是主线程更新后的最新值,而非任务分配时的原始值。 - 当所有初始任务被主线程快速分配完毕后,
loadingId停留在25,后续线程池空闲线程领取新任务时,lambda捕获的都是这个固定值25。
解决方案
方案1:用局部变量保存当前任务的loadingId
在run()方法中,将当前任务的loadingId赋值给一个局部变量——由于局部变量是effectively final(赋值后不再修改),lambda会捕获这个固定值,彻底避免主线程后续修改的影响:
修改后的run()方法:
@Override public void run() { do { //until requested to stop String imageFileName = obtainWait4nextReq(); // 用局部变量保存当前任务的loadingId,避免lambda捕获成员变量 Integer currentLoadingId = loadingId; System.out.println(" run assigns " + imageFileName + " - " + currentLoadingId); executor.execute(() -> { processLoadRequest(imageFileName, currentLoadingId); }); } while(!reqToFinish); }
方案2:封装任务信息为独立对象(更优雅)
创建一个包含loadingId和fileName的任务类,让obtainWait4nextReq()返回这个对象,彻底消除对成员变量的依赖,代码结构更清晰:
- 添加内部任务类:
private static class LoadTask { private final Integer loadingId; private final String fileName; public LoadTask(Integer loadingId, String fileName) { this.loadingId = loadingId; this.fileName = fileName; } public Integer getLoadingId() { return loadingId; } public String getFileName() { return fileName; } }
- 修改
obtainWait4nextReq()方法:
private LoadTask obtainWait4nextReq() { boolean requestObtained = false; LoadTask task = null; while(!requestObtained) { synchronized(lock) { if (!loadRequestIds.isEmpty()) { Integer loadingId = loadRequestIds.remove(0); String imageFile = reqId2File.remove(loadingId); task = new LoadTask(loadingId, imageFile); requestObtained = true; } } if (!requestObtained) waitFor(1000); } return task; }
- 修改
run()方法:
@Override public void run() { do { //until requested to stop LoadTask task = obtainWait4nextReq(); System.out.println(" run assigns " + task.getFileName() + " - " + task.getLoadingId()); executor.execute(() -> { processLoadRequest(task.getFileName(), task.getLoadingId()); }); } while(!reqToFinish); }
额外优化:移除不必要的线程映射
可以删除thread2loadingId成员变量——因为processLoadRequest方法已经通过参数持有当前任务的loadingId,无需通过线程映射获取,减少并发风险:
合并后的processLoadRequest方法:
private void processLoadRequest(String imageFileName, Integer localLoadingId) { Thread currentThread = Thread.currentThread(); System.out.println("Thread " + currentThread.getId() + " is assigned loading image " + imageFileName + " - " + localLoadingId); System.out.println("beginning to load/read loadingId= "+imageFileName + " - " + localLoadingId); Image image = null; boolean loadFailed = false; if (!MediaMetadataReader.isVideoFile(imageFileName)) { try { image = loadAndResizeImage(imageFileName); } catch (IOException e) { e.printStackTrace(); System.out.println("Failed to load image file " + imageFileName+ " - " + localLoadingId); loadFailed = true; } } synchronized(lock) { if (!loadFailed) { System.out.println("adding to Cache - " + localLoadingId); reqId2Image.put(localLoadingId, image); } } System.out.println("Thread " + currentThread.getId() + " finished loading image " + imageFileName + " - " + localLoadingId); }
内容的提问来源于stack exchange,提问作者inor
相关产品推荐
相关产品推荐

