94

SimpleDateFormat 的 javadoc 声明 SimpleDateFormat 未同步。

“日期格式不同步,建议为每个线程创建单独的格式实例,如果多个线程同时访问一个格式,必须对外同步。”

但是在多线程环境中使用 SimpleDateFormat 实例的最佳方法是什么。以下是我想到的一些选项,我过去使用过选项 1 和 2,但我很想知道是否有更好的选择,或者这些选项中的哪一个可以提供最佳性能和并发性。

选项 1:在需要时创建本地实例

public String formatDate(Date d) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(d);
}

选项 2:将 SimpleDateFormat 的实例创建为类变量,但同步对其的访问。

private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
    synchronized(sdf) {
        return sdf.format(d);
    }
}

选项 3:创建一个 ThreadLocal 来为每个线程存储不同的 SimpleDateFormat 实例。

private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public String formatDate(Date d) {
    SimpleDateFormat sdf = tl.get();
    if(sdf == null) {
        sdf = new SimpleDateFormat("yyyy-MM-hh");
        tl.set(sdf);
    }
    return sdf.format(d);
}
4

9 回答 9

44
  1. 创建 SimpleDateFormat 是昂贵的。除非很少使用,否则不要使用它。

  2. 好的,如果你能忍受一点阻塞。如果 formatDate() 使用不多,请使用。

  3. 如果您重用线程(线程池),则最快的选择。使用比 2 更多的内存,并且具有更高的启动开销。

对于应用程序 2. 和 3. 都是可行的选择。哪个最适合您的情况取决于您的用例。提防过早的优化。仅当您认为这是一个问题时才这样做。

对于第 3 方将使用的库,我将使用选项 3。

于 2010-11-05T16:41:41.827 回答
25

另一个选项是 Commons Lang FastDateFormat,但您只能将其用于日期格式而不是解析。

与 Joda 不同,它可以作为格式化的替代品。(更新:从 v3.3.2 开始,FastDateFormat 可以生成FastDateParser,它是 SimpleDateFormat 的插入式线程安全替代品)

于 2011-03-03T18:47:50.370 回答
20

如果您使用的是 Java 8,则可能需要使用java.time.format.DateTimeFormatter

这个类是不可变的和线程安全的。

例如:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new java.util.Date().toInstant()
                                 .atZone(ZoneId.systemDefault())
                                 .format(formatter);
于 2014-10-23T16:54:59.567 回答
6

Commons Lang 3.x 现在有 FastDateParser 和 FastDateFormat。它是线程安全的,比 SimpleDateFormat 更快。它还使用与 SimpleDateFormat 相同的格式/解析模式规范。

于 2014-03-19T18:24:01.257 回答
4

Don't use SimpleDateFormat, use joda-time's DateTimeFormatter instead. It is a bit stricter in the parsing side and so isn't quite a drop in replacement for SimpleDateFormat, but joda-time is much more concurrent friendly in terms of safety and performance.

于 2010-11-07T23:55:50.190 回答
3

我想说,为 SimpleDateFormat 创建一个简单的包装类,它可以同步对 parse() 和 format() 的访问,并且可以用作替代品。比你的选项#2 更简单,比你的选项#3 更简单。

似乎使 SimpleDateFormat 不同步对于 Java API 设计者来说是一个糟糕的设计决策;我怀疑是否有人期望 format() 和 parse() 需要同步。

于 2013-12-09T17:45:01.363 回答
1

另一种选择是将实例保存在线程安全队列中:

import java.util.concurrent.ArrayBlockingQueue;
private static final int DATE_FORMAT_QUEUE_LEN = 4;
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN);
// thread-safe date time formatting
public String format(Date date) {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    String text = fmt.format(date);
    dateFormatQueue.offer(fmt);
    return text;
}
public Date parse(String text) throws ParseException {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    Date date = null;
    try {
        date = fmt.parse(text);
    } finally {
        dateFormatQueue.offer(fmt);
    }
    return date;
}

dateFormatQueue 的大小应该接近估计的线程数,这些线程可以同时调用这个函数。在最坏的情况下,实际同时使用所有实例的线程数超过此数量,将创建一些 SimpleDateFormat 实例,因为它已满而无法返回到 dateFormatQueue。这不会产生错误,它只会导致创建一些只使用一次的 SimpleDateFormat 的惩罚。

于 2014-05-22T11:53:04.523 回答
1

我刚刚使用选项 3 实现了这一点,但做了一些代码更改:

  • ThreadLocal 通常应该是静态的
  • 似乎更干净地覆盖 initialValue() 而不是测试 if (get() == null)
  • 您可能想要设置区域设置和时区,除非您真的想要默认设置(Java 的默认设置很容易出错)

    private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US);
            sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
            return sdf;
        }
    };
    public String formatDate(Date d) {
        return tl.get().format(d);
    }
    
于 2016-02-23T07:26:31.980 回答
-1

假设您的应用程序有一个线程。那你为什么要同步对 SimpleDataFormat 变量的访问呢?

于 2014-02-23T03:03:14.040 回答