SimpleDateFormat解析日期随机抛出空字符串NumberFormatException异常
问题核心原因
SimpleDateFormat 是线程不安全类,只要你将该实例作为类的成员变量、静态变量或单例对象在多线程环境下共享,多个线程同时调用parse方法时会并发修改其内部的Calendar、DigitList等共享属性,就会出现你遇到的随机非必现异常:
- 输入的日期格式完全正常但随机报错
- 单步调试、表达式求值时因为是单线程执行无并发冲突,所以能正常返回结果
- 异常栈中出现的
digitList值异常、Long.parseLong收到空字符串,都是共享属性被其他线程篡改后的中间值
关于JDK版本差异的疑问
JDK 1.8.0_261和1.8.0_291版本对SimpleDateFormat的核心解析逻辑没有做兼容性变更,你本地未复现、远程复现的差异大概率是本地测试时并发量低未触发并发修改冲突,远程服务并发量更高更容易触发问题,和版本无关。
解决方案
你可以根据业务场景选择以下任意一种方案修复:
- 每次调用解析逻辑时单独创建
SimpleDateFormat实例,不做全局共享,并发量较低的场景可直接使用 - 用
ThreadLocal封装SimpleDateFormat,保证每个线程持有独立的实例,避免并发修改,示例代码:
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("dd/MM/yyyy", Locale.US)); // 调用时使用 return DATE_FORMAT_THREAD_LOCAL.get().parse((String)properties.get("validfrom"));
- (推荐)JDK8及以上版本直接使用
java.time包下的线程安全类DateTimeFormatter替换SimpleDateFormat,彻底避免线程安全问题,示例代码:
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy", Locale.US); // 解析时使用 LocalDate localDate = LocalDate.parse((String)properties.get("validfrom"), DATE_FORMATTER); // 如需转Date对象可按以下逻辑转换 Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
内容的提问来源于stack exchange,提问作者hovibi9445
相关产品推荐
相关产品推荐

