同步访问SimpleDateFormat


90

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);
}

10
+1提出这个问题。许多人认为SimpleDateFormat是线程安全的(我到处都可以看到假设)。
亚当·根特

有关ThreadLocal方法的更多信息,请参见:javaspecialists.eu/archive/Issue172.html
miner49r 2012年

而对于为什么,看到这个问题:stackoverflow.com/questions/6840803/...
Raedwald

@ 3urdoch您是否在选项2中错误地跳过了“静态”关键字?
M Faisal Hameed,

Answers:


43
  1. 创建SimpleDateFormat非常昂贵。除非很少做,否则不要使用它。

  2. 好吧,如果您可以忍受一点阻碍。如果formatDate()的使用量很少,则使用此方法。

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

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

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


如果我们使用Option-2并将其声明SimpleDateFormat为实例变量,则可以synchronized block使它成为线程安全的。但是声纳显示警告squid-AS2885。有什么办法可以解决声纳问题?
M. Faisal Hameed,

24

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

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


8
由于共享郎3.2,FastDateFormat具有parse()方法以及
manuna

19

如果您使用的是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);

6

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


它仅在3.2+而非3.x中可用
Wisteso

4

不要使用SimpleDateFormat,而应使用joda-time的DateTimeFormatter。它在解析方面更加严格,因此在替代SimpleDateFormat方面并没有什么下降,但是就安全性和性能而言,joda-time的并发友好性更高。


3

我想说的是,为SimpleDateFormat创建一个简单的包装器类,该包装器类可以同步对parse()和format()的访问,并且可以用作替代品。比您的选择2更可靠,比您的选择3更麻烦。

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


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的代价。


1

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

  • ThreadLocal通常应该是静态的
  • 似乎更干净的方法重写了initialValue()而不是测试是否(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);
    }

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.