日志阅读器中全局值被其他对象覆盖的原因排查
问题:日志阅读器中所有LogReader对象的错误字符串被最后一个值覆盖
场景说明
正在开发一款日志阅读器,用于统计自上次检查以来,YAML配置文件中指定错误的出现次数(已实现模拟日志写入,每5秒向日志文件写入一个指定错误)。
YAML配置示例:
logs: - path: path-to-file\logname.log error_names: - Error1t - Error2t - Error3t interval: 10
需要为每个错误创建独立的LogReader对象,并加入任务调度器循环执行,但出现异常:所有对象的错误字符串被最后传入的字符串覆盖。
相关代码
execute方法代码
public void execute() throws DfException, IOException, InterruptedException { int logFileAmount = config.getLogs().size(); for(int logId = 0; logId < logFileAmount; logId++){ var logConfig = config.getLogs().get(logId); var logReaderRerunTime = logConfig.getInterval() * 1000; var errors = logConfig.getErrorNames(); for(int errorId = 0; errorId < errors.size(); errorId++) { var errorText = logConfig.getErrorNames().get(errorId); System.out.println(errorText); logReader = new LogReader(errorText, registry, config); taskScheduler.scheduleWithFixedDelay(() -> { try { logReader.runLogReader(); } catch (IOException e) { e.printStackTrace(); } }, Duration.ofMillis(logReaderRerunTime)); } } }
输出异常示例
Error11t Error11t 0 Error22t Error22t 0 Error33t Error11t 0 Error33t 0 Error22t 0 Error33t 0 Error33t 0 Error33t 0 Error33t 0 Error33t 3 Error33t 3 Error33t 88
原因分析
问题出在lambda表达式对外部变量的捕获机制上:
- 循环中使用了外部可变变量
logReader,lambda捕获的是这个变量的引用,而非当前循环迭代时的对象实例。 - 循环执行到最后一次迭代时,
logReader会被赋值为最后一个LogReader对象,后续所有lambda任务执行时,都会调用这个最后一次赋值的对象的runLogReader()方法,导致所有任务都使用最后一个错误字符串。
解决方案
在循环内部创建局部的、独立的LogReader实例,让lambda捕获当前迭代的局部实例:
修改后的execute方法:
public void execute() throws DfException, IOException, InterruptedException { int logFileAmount = config.getLogs().size(); for(int logId = 0; logId < logFileAmount; logId++){ var logConfig = config.getLogs().get(logId); var logReaderRerunTime = logConfig.getInterval() * 1000; var errors = logConfig.getErrorNames(); for(int errorId = 0; errorId < errors.size(); errorId++) { var errorText = logConfig.getErrorNames().get(errorId); System.out.println(errorText); // 创建局部变量,每个循环迭代持有独立的实例 LogReader currentLogReader = new LogReader(errorText, registry, config); taskScheduler.scheduleWithFixedDelay(() -> { try { currentLogReader.runLogReader(); } catch (IOException e) { e.printStackTrace(); } }, Duration.ofMillis(logReaderRerunTime)); } } }
这样每个lambda都会捕获当前循环迭代对应的currentLogReader实例,不会被后续循环的赋值覆盖,每个任务都会使用自己对应的错误字符串执行统计。
内容的提问来源于stack exchange,提问作者Bambi2k21
相关产品推荐
相关产品推荐

