2010-11-05 112 views
81

SimpleDateFormat的javadoc指出SimpleDateFormat不同步。同步对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是线程安全的(我在各处都能看到假设)。 – 2011-03-03 18:50:39

+0

有关ThreadLocal方法的详细信息,请参阅: http://www.javaspecialists.eu/archive/Issue172.html – miner49r 2012-02-04 14:02:11

+0

为什么请参阅此问题:http://stackoverflow.com/questions/6840803/simpledateformat-线程安全 – Raedwald 2013-06-13 12:17:46

回答

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

  2. 好的,如果你可以忍受一点阻塞。如果formatDate()用处不大,则使用。

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

对于应用程序都2和3是可行的选择。哪个最适合你的情况取决于你的用例。谨防过早优化。只有在你认为这是一个问题时才会这样做。

对于库,将通过第三方使用我会使用选项3.

+0

如果我们使用Option-2并声明'SimpleDateFormat'作为一个实例变量,那么我们可以使用'synchronized block'使它成为线程安全的。但声纳显示警告[squid-AS2885](https://sonarqube.com/coding_rules#rule_key=squid%3AS2885)。有什么办法解决声纳问题吗? – 2016-08-28 23:38:31

4

不要使用SimpleDateFormat的,使用乔达时间的DateTimeFormatter代替。它在解析方面有点严格,所以不是SimpleDateFormat替代品的下降,但是在安全性和性能方面,joda-time更具并发性。

23

另一种选择是共享郎FastDateFormat,但你只能用它的日期格式,而不是解析。

与Joda不同,它可以作为格式化的直接替代品。 (更新:由于V3.3.2,FastDateFormat可以产生FastDateParser,这是一个下拉线程安全的替代品的SimpleDateFormat)

+8

由于Commons Lang 3.2,'FastDateFormat'也有'parse()'方法 – manuna 2014-04-24 10:52:42

3

我会说,创建一个简单的包装类的SimpleDateFormat的是同步访问解析( )和format(),可以用作插入式替换。比第二种选择更简单,比第三种选择更简单。

看起来像是制作SimpleDateFormat非同步是Java API设计者的一个糟糕的设计决定;我怀疑任何人都希望format()和parse()需要同步。

0

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

6

Commons Lang 3.x现在具有FastDateParser以及FastDateFormat。它比SimpleDateFormat更安全且更快速。它也使用与SimpleDateFormat相同的格式/分析模式规范。

+0

它只能用于3.2+而不是3.x – Wisteso 2017-07-07 16:23:39

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的惩罚。

11

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

我只是选择3实现这一点,但做了一些更改代码:

  • 的ThreadLocal通常应该是静态
  • 似乎清洁覆盖初值( )而不是测试if(get()== null)
  • 您可能想要设置语言环境和时间z一个除非你真的想要的默认设置(默认值是非常容易出错的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); 
    }