在Java中,SimpleDateFormat
是一个用于日期/时间格式化和解析的类,它不是线程安全的。这意味着如果多个线程同时访问同一个 SimpleDateFormat
实例,可能会导致不可预测的结果,如日期格式混乱。
如果你将 SimpleDateFormat
定义为静态变量(类变量),那么在类的整个生命周期中,这个变量只有一份实例,被所有类的实例共享。由于线程不安全的问题,如果多个线程并发访问这个静态的 SimpleDateFormat
,就很可能引发上述提到的线程安全问题。在高并发环境下,这会导致程序行为异常,数据损坏等问题。
public class MyClass {
public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 其他代码...
}
public class MyClass {
public static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 其他代码...
}
特别是第二种,当存在两种格式时更容易出现格式转换错误。
-
并发访问冲突:当多个线程同时尝试使用这些
SimpleDateFormat
实例进行日期格式化或解析时,可能会互相干扰其内部状态,因为它们没有内置的同步机制来防止并发操作。这种情况下,可能会导致日期解析错误,格式错误的结果,甚至抛出异常。 -
数据竞争:例如,一个线程在使用
sdf
格式化日期时,另一个线程可能正在修改simpleDateFormat
的内部状态来处理另一个格式,这将导致不可预测的结果。
ava.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:2082)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.cyweb.jt.listener.BDDzwlConsumerListener.bdHuiAnKkllWS(xxxxxxx.java:54)
at sun.reflect.GeneratedMethodAccessor126.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119)
at org.springframework.kafka.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:56)
dm.jdbc.driver.DMException: Invalid datetime value
at dm.jdbc.driver.DBError.throwException(DBError.java:635)
at dm.jdbc.c.a.n.J(MSG.java:221)
at dm.jdbc.c.a.n.G(MSG.java:181)
at dm.jdbc.c.a.n.F(MSG.java:162)
at dm.jdbc.c.a.a(DBAccess.java:759)
at dm.jdbc.c.a.a(DBAccess.java:311)
at dm.jdbc.c.a.a(DBAccess.java:401)
at dm.jdbc.driver.DmdbPreparedStatement.executeInner(DmdbPreparedStatement.java:221)
at dm.jdbc.driver.DmdbPreparedStatement.do_execute(DmdbPreparedStatement.java:310)
at dm.jdbc.driver.DmdbPreparedStatement.execute(DmdbPreparedStatement.java:1630)
为了避免这些问题,推荐的做法是:
- 使用ThreadLocal:为每个线程提供单独的
SimpleDateFormat
实例,确保线程安全。这样每个线程都有自己的格式化对象,互不影响。
修改示例如下:
public class MyClass {
private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTER_SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static Date parse(String dateStr, boolean isFullFormat) throws ParseException {
return isFullFormat ? DATE_FORMATTER.get().parse(dateStr) : DATE_FORMATTER_SDF.get().parse(dateStr);
}
public static String format(Date date, boolean isFullFormat) {
return isFullFormat ? DATE_FORMATTER.get().format(date) : DATE_FORMATTER_SDF.get().format(date);
}
// 其他代码...
}
通过这种方式,即使在高并发环境中,每个线程也能够安全地使用自己独立的SimpleDateFormat
实例,从而避免了线程安全问题。或者直接使用非静态方法。
非静态变量使用
如果将 SimpleDateFormat
定义为非静态变量(实例变量),那么每个类的实例都会拥有自己独立的 SimpleDateFormat
实例。这样可以避免多线程间的干扰,因为每个对象处理自己的格式化器。然而,在大量创建对象的情况下,这将消耗更多的内存资源。
public class MyClass {
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 其他代码...
}
解决策略
为了确保线程安全,有几种策略可以采用:
-
局部变量: 每次使用时创建一个新的
SimpleDateFormat
实例并在使用后丢弃,这样可以保证线程安全,但频繁创建实例可能影响性能。 -
同步访问: 尽管不推荐(因为会影响性能),但可以通过同步代码块或方法来保护对静态
SimpleDateFormat
的访问。 -
ThreadLocal。
综上所述,将 SimpleDateFormat
设为静态变量虽然在单一线程环境中或无需考虑线程安全的场景下可以节省资源,但在多线程环境中,为了保证线程安全,通常推荐使用非静态变量或采用如 ThreadLocal
的机制来管理此类实例。