“Java DateFormat不是线程安全的”这是什么导致?

大家注意Java DateFormat不是线程安全的,理论上我理解这个概念。

但是由于这个原因,我无法想像我们可以面对哪些实际问题。 说,我在一个类中有一个DateFormat字段,并且在multithreading环境中在类中的不同方法(格式化date)中使用相同的。

这是否会导致:

  • 任何exception,如格式exception
  • 数据差异
  • 任何其他问题?

另外,请解释原因。

让我们试试看。

这是一个程序,其中多个线程使用共享的SimpleDateFormat

计划

 public static void main(String[] args) throws Exception { final DateFormat format = new SimpleDateFormat("yyyyMMdd"); Callable<Date> task = new Callable<Date>(){ public Date call() throws Exception { return format.parse("20101022"); } }; //pool with 5 threads ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<Date>> results = new ArrayList<Future<Date>>(); //perform 10 date conversions for(int i = 0 ; i < 10 ; i++){ results.add(exec.submit(task)); } exec.shutdown(); //look at the results for(Future<Date> result : results){ System.out.println(result.get()); } } 

运行几次,你会看到:

例外

这里有几个例子:

1。

 Caused by: java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48) at java.lang.Long.parseLong(Long.java:431) at java.lang.Long.parseLong(Long.java:468) at java.text.DigitList.getLong(DigitList.java:177) at java.text.DecimalFormat.parse(DecimalFormat.java:1298) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) 

2。

 Caused by: java.lang.NumberFormatException: For input string: ".10201E.102014E4" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1303) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) 

3。

 Caused by: java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084) at java.lang.Double.parseDouble(Double.java:510) at java.text.DigitList.getDouble(DigitList.java:151) at java.text.DecimalFormat.parse(DecimalFormat.java:1303) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312) 

不正确的结果

 Sat Oct 22 00:00:00 BST 2011 Thu Jan 22 00:00:00 GMT 1970 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Thu Oct 22 00:00:00 GMT 1970 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 

正确的结果

 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 Fri Oct 22 00:00:00 BST 2010 

在multithreading环境中安全使用DateFormats的另一种方法是使用ThreadLocalvariables来保存DateFormat对象,这意味着每个线程都有自己的副本,不需要等待其他线程释放它。 这是如何:

 public class DateFormatTest { private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd"); } }; public Date convert(String source) throws ParseException{ Date d = df.get().parse(source); return d; } } 

这是一个很好的职位 ,更多的细节。

我期望数据损坏 – 例如,如果你同时parsing两个date,你可能会有一个来自另一个数据的污染。

很容易想象会发生这种情况:parsing通常涉及维持一定数量的状态,至今为止您已阅读的内容。 如果两个线程都在相同的状态下践踏,你会遇到问题。 例如, DateFormat公开Calendartypes的calendar字段,并查看SimpleDateFormat的代码,一些方法调用calendar.set(...)和其他方法调用calendar.get(...) 。 这显然不是线程安全的。

我没有看到为什么DateFormat不是线程安全的确切细节,但是对于我来说,只要知道它没有同步就是不安全的 – 非安全的确切方式甚至可以在不同版本之间改变。

就我个人而言,我会使用乔达时间的parsing器,因为它们线程安全的 – 而乔达时间是一个更好的date和时间API开始:)

粗略地说,你不应该定义一个DateFormat作为被许multithreading访问的对象的实例variables,或者static

date格式不同步。 build议为每个线程创build单独的格式实例。

所以,如果您的Foo.handleBar(..)被多个线程访问,而不是:

 public class Foo { private DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); public void handleBar(Bar bar) { bar.setFormattedDate(df.format(bar.getStringDate()); } } 

你应该使用:

 public class Foo { public void handleBar(Bar bar) { DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); bar.setFormattedDate(df.format(bar.getStringDate()); } } 

此外,在所有情况下,不要有一个static DateFormat

正如Jon Skeet所指出的那样,如果你执行外部同步,你可以同时拥有一个静态的和一个共享的实例variables(例如,围绕对DateFormat调用进行synchronized

如果您使用的是Java 8,那么您可以使用DateTimeFormatter

从模式创build的格式化程序可以根据需要多次使用,它是不可变的,并且是线程安全的。

码:

 LocalDate date = LocalDate.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); String text = date.format(formatter); System.out.println(text); 

输出:

 2017-04-17 

date格式不同步。 build议为每个线程创build单独的格式实例。 如果多个线程同时访问一个格式,它必须在外部同步。

这意味着假设你有一个DateFormat对象,并且你从两个不同的线程访问同一个对象,并且你正在调用这个对象的格式化方法,那么这两个线程将会在同一个对象上同时进入同一个方法,这样你就可以看到它赢了没有得到正确的结果

如果你必须使用DateFormat,那么你应该怎么做

 public synchronized myFormat(){ // call here actual format method } 
  • 参考

数据已损坏。 昨天我在我的multithreading程序中注意到了,它有静态的DateFormat对象,并通过JDBC调用它的format()来读取值。 我有SQL select语句,我用不同的名称( SELECT date_from, date_from AS date_from1 ... )读取相同的date。 WHERE clasue中的这些语句在5个线程中使用了各种date。 date看上去是“正常的”,但是它们的价值有所不同 – 而所有的date都是在同一年只有一个月和一天发生了变化。

其他答案告诉你如何避免这种腐败。 我让我的DateFormat不是静态的,现在它是一个调用SQL语句的类的成员。 我同时testing了静态版本。 两者都performance良好,没有差异。

Format,NumberFormat,DateFormat,MessageFormat等的规范没有被devise为线程安全的。 此外,parsing方法调用Calendar.clone()方法,它会影响日历占位符,因此多个线程并发parsing将更改Calendar实例的克隆。

更多的,这些是这样的错误报告, 这与DateFormat线程安全问题的结果。

如果有多个线程操作/访问一个单一的DateFormat实例并且不使用同步,那么可能会得到混乱的结果。 这是因为多个非primefaces操作可能会改变状态或看到不一致的内存。

这是我简单的代码,显示DateFormat不是线程安全的。

 import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class DateTimeChecker { static DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); public static void main(String args[]){ String target1 = "Thu Sep 28 20:29:30 JST 2000"; String target2 = "Thu Sep 28 20:29:30 JST 2001"; String target3 = "Thu Sep 28 20:29:30 JST 2002"; runThread(target1); runThread(target2); runThread(target3); } public static void runThread(String target){ Runnable myRunnable = new Runnable(){ public void run(){ Date result = null; try { result = df.parse(target); } catch (ParseException e) { e.printStackTrace(); System.out.println("Ecxfrt"); } System.out.println(Thread.currentThread().getName() + " " + result); } }; Thread thread = new Thread(myRunnable); thread.start(); } } 

由于所有线程都使用相同的SimpleDateFormat对象,因此会引发以下exception。

 Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source) at java.text.DigitList.getDouble(Unknown Source) at java.text.DecimalFormat.parse(Unknown Source) at java.text.SimpleDateFormat.subParse(Unknown Source) at java.text.SimpleDateFormat.parse(Unknown Source) at java.text.DateFormat.parse(Unknown Source) at DateTimeChecker$1.run(DateTimeChecker.java:24) at java.lang.Thread.run(Unknown Source) 

但是,如果我们将不同的对象传递给不同的线程,代码运行没有错误。

 import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class DateTimeChecker { static DateFormat df; public static void main(String args[]){ String target1 = "Thu Sep 28 20:29:30 JST 2000"; String target2 = "Thu Sep 28 20:29:30 JST 2001"; String target3 = "Thu Sep 28 20:29:30 JST 2002"; df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target1, df); df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target2, df); df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); runThread(target3, df); } public static void runThread(String target, DateFormat df){ Runnable myRunnable = new Runnable(){ public void run(){ Date result = null; try { result = df.parse(target); } catch (ParseException e) { e.printStackTrace(); System.out.println("Ecxfrt"); } System.out.println(Thread.currentThread().getName() + " " + result); } }; Thread thread = new Thread(myRunnable); thread.start(); } } 

这是结果。

 Thread-0 Thu Sep 28 17:29:30 IST 2000 Thread-2 Sat Sep 28 17:29:30 IST 2002 Thread-1 Fri Sep 28 17:29:30 IST 2001