同步对SimpleDateFormat的访问

时间:2010-11-05 16:21:14

标签: java multithreading concurrency simpledateformat

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

9 个答案:

答案 0 :(得分:42)

  1. 创建SimpleDateFormat是expensive。除非很少这样做,否则不要使用它。

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

  3. 最快的选项如果您重复使用线程(thread pool)。使用的内存多于2,并且启动开销较高。

  4. 对于应用程序,2和3都是可行的选择。哪种情况最适合您的情况取决于您的使用案例。注意过早优化。只有你认为这是一个问题才能做到。

    对于第三方使用的图书馆,我会使用选项3。

答案 1 :(得分:24)

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

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

答案 2 :(得分:15)

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

  

此类是不可变且线程安全的。

e.g:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new java.util.Date().toInstant()
                                 .atZone(ZoneId.systemDefault())
                                 .format(formatter);

答案 3 :(得分:6)

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

答案 4 :(得分:4)

不要使用SimpleDateFormat,而是使用joda-time的DateTimeFormatter。它在解析方面有点严格,因此不会替代SimpleDateFormat,但joda-time在安全性和性能方面更加友好。

答案 5 :(得分:3)

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

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

答案 6 :(得分: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。

答案 7 :(得分: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);
    }
    

答案 8 :(得分:0)

想象一下你的应用程序有一个帖子。为什么要同步对SimpleDateFormat变量的访问?

相关问题