线上告警群出现告警啦,下面是具体的告警堆栈日志
java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:601)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
一、原因分析:
以上异常的原因是 SimpleDateFormat 是线程不安全的,因为 SimpleDateFormat 是有状态的,如 DateFormat 类中含有非线程安全的 NumberFormat 成员变量:
从 SimpleDateFormat 的 Doc 中能看到如下描述:
二、修改方案:
-
方案一:
每次都新建 SimpleDateFormat 类使用
-
方案二:
针对 SimpleDateFormat 类的线程安全问题,apache commons-lang 包提供了 FastDateFormat 类。
该修复方案相对来说代码改造量最小,仅需在声明静态 SimpleDateFormat 实例代码处将 SimpleDateFormat 实例替换为 FastDateFormat 实例。
示例代码如下:
public static final FastDateFormat yyyMMddFormat = FastDateFormat.getInstance("yyyy-MM-dd");
三、案发现场复现
public class SimpleDateFormatTest {
private static final AtomicBoolean STOP = new AtomicBoolean();
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd"); // 非线程安全
public static void main(String[] args) {
Runnable runnable = () -> {
int count = 0;
while (!STOP.get()) {
try {
FORMATTER.parse("2025-04-27");
} catch (Exception e) {
e.printStackTrace();
if (++count > 3) {
STOP.set(true);
}
}
}
};
new Thread(runnable).start();
new Thread(runnable).start();
}
}
而后使用 FastDateFormat 不再报错
public class FastDateFormatTest {
private static final AtomicBoolean STOP = new AtomicBoolean();
private static final FastDateFormat FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd"); // 线程安全
public static void main(String[] args) {
Runnable runnable = () -> {
int count = 0;
while (!STOP.get()) {
try {
FORMATTER.parse("2023-7-15");
} catch (Exception e) {
e.printStackTrace();
if (++count > 3) {
STOP.set(true);
}
}
}
};
new Thread(runnable).start();
new Thread(runnable).start();
}
}