Java中DecimalFormat解析偶发空字符串NumberFormatException求助
问题分析:DecimalFormat.parse()偶尔抛出空字符串NumberFormatException
问题背景
在Java 11.0.20+8-LTS-sapmachine环境下,一段数字解析代码偶尔会抛出java.lang.NumberFormatException: For input string: ""异常。
最初误提供的代码
用户最初误以为使用的是如下代码:
static Number parse(String s) { if (s == null || s.trim().length() == 0) { return 0; } return Long.parseLong(s); }
用户疑问:既然已经通过s.trim().length() != 0判断字符串非空,空字符串是如何传入parseLong()的?
更新后的实际代码
用户补充了关键信息:实际使用DecimalFormat进行解析,代码如下:
static java.text.DecimalFormat df = new java.text.DecimalFormat("#"); static Number parse(String s) throws java.text.ParseException { if (s == null || s.trim().length() == 0) { return 0; } return df.parse(s); }
异常堆栈信息
生产环境中抛出的异常堆栈:
java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:702) at java.lang.Long.parseLong(Long.java:817) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2121) at java.text.NumberFormat.parse(NumberFormat.java:429)
异常原因分析
- DecimalFormat非线程安全:
DecimalFormat是非线程安全的类,代码中将其声明为static全局变量,当多个线程同时调用parse()方法时,会导致该实例的内部解析状态被并发修改,出现混乱。比如一个线程正在解析某字符串时,另一个线程的操作可能导致内部缓冲区被重置或覆盖,最终使得DigitList.getLong()收到空字符串输入,触发Long.parseLong("")抛出异常。 - trim()判断不覆盖所有异常场景:虽然
s.trim().length() == 0能过滤掉空白字符串,但DecimalFormat对某些非空白字符串的解析仍可能触发内部逻辑错误(不过此次异常的核心原因还是多线程下的状态破坏)。
解决方案
- 使用ThreadLocal隔离线程实例:为每个线程分配独立的
DecimalFormat实例,避免并发状态干扰:private static final ThreadLocal<java.text.DecimalFormat> DF_THREAD_LOCAL = ThreadLocal.withInitial(() -> new java.text.DecimalFormat("#")); static Number parse(String s) throws java.text.ParseException { if (s == null || s.trim().isEmpty()) { return 0; } return DF_THREAD_LOCAL.get().parse(s); } - 每次调用创建新实例:如果解析频率不高,也可以在每次调用
parse()时创建新的DecimalFormat对象,避免线程安全问题。 - 同步锁(不推荐):在调用
df.parse()时加同步锁,会导致多线程场景下性能下降,仅适合低并发场景。
内容的提问来源于stack exchange,提问作者Jon
相关产品推荐
相关产品推荐

