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

2022-08-31 07:27:15

每个人都对Java DateFormat不是线程安全的警告,我从理论上理解这个概念。

但是我无法想象由于这个原因我们可以面临哪些实际问题。假设我在一个类中有一个 DateFormat 字段,并且在多线程环境中的类中的不同方法(格式化日期)中使用相同的字段。

这会导致:

  • 任何异常,如格式异常
  • 数据差异
  • 还有其他问题吗?

另外,请解释原因。


答案 1

让我们来试试吧。

下面是一个程序,其中多个线程使用共享 .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

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

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

这是一篇包含更多详细信息的好帖子


答案 2

我预计数据损坏 - 例如,如果您同时解析两个日期,则一个调用可能会被另一个调用的数据污染。

很容易想象这是如何发生的:解析通常涉及保持一定程度的状态,以了解到目前为止您所阅读的内容。如果两个线程都踩在同一状态上,你会遇到问题。例如,公开一个类型的字段,并查看 的代码,一些方法调用,而另一些方法调用 。这显然不是线程安全的。DateFormatcalendarCalendarSimpleDateFormatcalendar.set(...)calendar.get(...)

我还没有研究为什么线程安全的确切细节,但对我来说,知道没有同步是不安全的就足够了 - 非安全的确切方式甚至可能在发布之间发生变化。DateFormat

就个人而言,我会使用Joda Time的解析器,因为它们线程安全的 - 而Joda Time是一个更好的日期和时间API,可以从:)