Predicate处理日期时出现线程安全的ArrayIndexOutOfBoundsException问题
根因定位
你遇到的偶发数组越界异常完全是 SimpleDateFormat 的线程安全问题导致的:
SimpleDateFormat内部维护了一个共享的Calendar实例,调用format/parse方法时会修改该实例的状态,官方文档明确标注该类不适用于多线程并发场景- 你的代码中使用了
parallelStream()并行流处理实体,多线程会同时调用成员变量formatter的format方法,Calendar内部状态被并发篡改,最终触发ArrayIndexOutOfBoundsException
验证方法
将 entities.parallelStream() 改为 entities.stream() 串行执行,重新测试,异常会彻底消失,即可100%确认是该问题。
修复方案
推荐按优先级选择以下方案:
- Java 8+优先使用DateTimeFormatter(最优)
DateTimeFormatter是JDK8新增的日期格式化类,天生线程安全,可直接作为静态全局变量使用,无并发风险:// 定义全局格式化器 private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy") .withZone(ZoneId.systemDefault()); // Predicate中调用,Date对象先转Instant predicates.add(new SimpleEntry<>("date", entity -> entity.getDate() != null && dateStr.equalsIgnoreCase(DATE_FORMATTER.format(entity.getDate().toInstant())))); - 用ThreadLocal封装SimpleDateFormat(兼容低版本JDK)
每个线程持有独立的SimpleDateFormat实例,避免重复创建的开销,同时解决并发问题:private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = ThreadLocal.withInitial(() -> { SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); sdf.setTimeZone(TimeZone.getDefault()); return sdf; }); // 调用时使用 DATE_FORMATTER.get().format(entity.getDate()) - 局部创建SimpleDateFormat实例(最简单)
直接在Predicate的lambda内部每次创建新的SimpleDateFormat实例,逻辑最直白,适合性能要求不高的场景:predicates.add(new SimpleEntry<>("date", entity -> { if (entity.getDate() == null) return false; SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); sdf.setTimeZone(TimeZone.getDefault()); return dateStr.equalsIgnoreCase(sdf.format(entity.getDate())); }));
内容的提问来源于stack exchange,提问作者Dogukan Evcil
相关产品推荐
相关产品推荐

