多线程环境下含静态SimpleDateFormat的静态方法问题咨询
多线程环境下SimpleDateFormat的问题与解决方案
首先咱们来拆解你这段代码在多线程场景下会遇到的核心问题,再给出对应的靠谱解决方案:
存在的线程安全问题
SimpleDateFormat本身线程不安全:这个类内部维护了可变的状态(比如内置的Calendar实例),当多个线程同时调用setTimeZone或者format方法时,会互相篡改对方的内部状态,导致输出的时间字符串出现各种错乱——比如日期和时间不匹配、格式混乱,甚至直接抛出ArrayIndexOutOfBoundsException这类异常。- 静态实例的共享冲突:你把
SDF_ISO_DATE和SDF_ISO_TIME定义为静态常量,意味着所有线程都会共享这两个实例。哪怕你每次调用都设置UTC时区,这个设置操作本身也是线程不安全的:线程A刚设置完时区,线程B可能马上覆盖掉,或者在格式化过程中时区被中途修改,最终的时间结果完全不符合预期。
解决方案
这里按推荐程度给你列几种可行方案:
1. 使用Java 8+的日期时间API(最推荐)
Java 8引入的java.time包下的类都是线程安全且不可变的,完美替代老旧的SimpleDateFormat和Date。改写后的代码简洁又安全:
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_TIME; public static String getTimeStampAsString(final long time) { Instant instant = Instant.ofEpochMilli(time); LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneOffset.UTC); return DATE_FORMATTER.format(dateTime) + " " + TIME_FORMATTER.format(dateTime); }
DateTimeFormatter是线程安全的单例,Instant、LocalDateTime这类对象也不会被修改,多线程下调用完全没有冲突。
2. 每次调用时创建新的SimpleDateFormat实例
如果因为兼容性原因无法升级到Java 8+,可以在方法内部每次调用时创建新的SimpleDateFormat对象,彻底避免线程共享:
public static String getTimeStampAsString(final long time) { TimeZone tz = TimeZone.getTimeZone("UTC"); SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd"); sdfDate.setTimeZone(tz); SimpleDateFormat sdfTime = new SimpleDateFormat("HH:mm:ss"); sdfTime.setTimeZone(tz); return sdfDate.format(new Date(time)) + " " + sdfTime.format(new Date(time)); }
缺点是每次创建对象会有轻微的性能开销,但绝大多数业务场景下这个开销可以忽略不计。
3. 用ThreadLocal存储专属实例
如果担心频繁创建对象的性能问题,可以用ThreadLocal为每个线程分配独立的SimpleDateFormat实例,每个线程只会初始化一次:
private static final ThreadLocal<SimpleDateFormat> DATE_THREAD_LOCAL = ThreadLocal.withInitial(() -> { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); return sdf; }); private static final ThreadLocal<SimpleDateFormat> TIME_THREAD_LOCAL = ThreadLocal.withInitial(() -> { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); return sdf; }); public static String getTimeStampAsString(final long time) { return DATE_THREAD_LOCAL.get().format(new Date(time)) + " " + TIME_THREAD_LOCAL.get().format(new Date(time)); }
每个线程调用get()时会拿到自己专属的实例,不会和其他线程产生冲突,性能也比较稳定。
内容的提问来源于stack exchange,提问作者Don Jose
相关产品推荐
相关产品推荐

