同步对SimpleDateFormat的访问
SimpleDateFormat的javadoc声明SimpleDateFormat不同步。
“date格式不同步,build议为每个线程创build单独的格式实例,如果多个线程同时访问一个格式,则必须在外部进行同步。
但在multithreading环境中使用SimpleDateFormat实例的最佳方法是什么? 以下是我想到的一些选项,过去我使用了选项1和2,但是我很好奇是否有更好的select,或者哪些选项可以提供最佳的性能和并发性。
选项1:在需要时创build本地实例
public String formatDate(Date d) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); return sdf.format(d); }
选项2:创buildSimpleDateFormat的一个实例作为类variables,但同步对它的访问。
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); public String formatDate(Date d) { synchronized(sdf) { return sdf.format(d); } }
选项3:创build一个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); }
-
创buildSimpleDateFormat是昂贵的 。 除非很less做,否则不要使用它。
-
好的,如果你可以忍受一点阻塞。 如果formatDate()使用得不多。
-
如果你重用线程( 线程池 ),最快的select。 使用比2.更多的内存,并具有较高的启动开销。
对于应用程序来说,2.和3.是可行的选项。 哪个最适合你的情况取决于你的用例。 谨防过早优化。 只有这样做,如果你认为这是一个问题。
对于第三方使用的库,我会使用选项3。
另一个选项是Commons Lang FastDateFormat,但只能用于date格式化而不能parsing。
与Joda不同,它可以作为格式化的直接替代品。 (更新:从v3.3.2开始,FastDateFormat可以生成一个FastDateParser ,它是SimpleDateFormat的一个简单线程安全的替代品)
如果您正在使用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);
Commons Lang 3.x现在具有FastDateParser以及FastDateFormat。 它比SimpleDateFormat线程安全并且更快。 它也使用与SimpleDateFormat相同的格式/分析模式规范。
不要使用SimpleDateFormat,而是使用joda-time的DateTimeFormatter。 在parsing方面有些严格,所以SimpleDateFormat的替代性不是很大,但是在安全性和性能方面,joda-time更友好。
我想说,为SimpleDateFormat创build一个简单的包装类,用于同步对parse()和format()的访问,并且可以用作插入replace。 比你的select#2更简单,比你的选项#3less一些麻烦。
似乎使SimpleDateFormat不同步是Java APIdevise人员糟糕的devise决定; 我怀疑有人希望format()和parse()需要同步。
另一种select是将实例保留在线程安全队列中:
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实例将被创build,因为它是满的,所以不能返回到dateFormatQueue。 这不会产生错误,只会产生一些SimpleDateFormat的惩罚,这些SimpleDateFormat只能使用一次。
想象一下你的应用程序有一个线程。 为什么要同步访问SimpleDataFormatvariables呢?
我只是用Option 3来实现,但是做了一些代码改变:
- ThreadLocal通常应该是静态的
- 似乎更清洁覆盖initialValue()而不是testingif(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); }