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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4021151/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 08:32:26  来源:igfitidea点击:

"Java DateFormat is not threadsafe" what does this leads to?

javamultithreadingdate-format

提问by haps10

Everybody cautions regarding Java DateFormat not being thread safe and I understand the concept theoretically.

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

But I'm not able to visualize what actual issues we can face due to this. Say, I've a DateFormat field in a class and the same is used in different methods in the class (formatting dates) in a multi-threaded environment.

但我无法想象我们会因此面临哪些实际问题。说,我在一个类中有一个 DateFormat 字段,并且在多线程环境中该类中的不同方法(格式化日期)中使用了相同的字段。

Will this cause:

这会导致:

  • any exception like format exception
  • discrepancy in data
  • any other issue?
  • 任何异常,如格式异常
  • 数据差异
  • 还有其他问题吗?

Also, please explain why.

另外,请说明原因。

采纳答案by dogbane

Let's try it out.

让我们试试看。

Here is a program in which multiple threads use a shared SimpleDateFormat.

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

Program:

程序

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

Run this a few times and you will see:

运行几次,你会看到:

Exceptions:

例外

Here are a few examples:

这里有一些例子:

1.

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.

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.

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)

Incorrect Results:

结果不正确

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

Correct Results:

正确结果

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


Another approach to safely use DateFormats in a multi-threaded environment is to use a ThreadLocalvariable to hold the DateFormatobject, which means that each thread will have its own copy and doesn't need to wait for other threads to release it. This is how:

在多线程环境中安全使用 DateFormats 的另一种方法是使用一个 ThreadLocal变量来保存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;
  }
}

Here is a good postwith more details.

这是一篇有更多细节的好帖子

回答by Bozho

Roughly, that you should not define a DateFormatas instance variable of an object accessed by many threads, or static.

粗略地说,你不应该定义一个DateFormat被许多线程访问的对象的实例变量,或者static.

Date formats are not synchronized. It is recommended to create separate format instances for each thread.

日期格式不同步。建议为每个线程创建单独的格式实例。

So, in case your Foo.handleBar(..)is accessed by multiple threads, instead of:

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

public class Foo {
    private DateFormat df = new SimpleDateFormat("dd/mm/yyyy");

    public void handleBar(Bar bar) {
        bar.setFormattedDate(df.format(bar.getStringDate());  
    }
}

you should use:

你应该使用:

public class Foo {

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

Also, in all cases, don't have a staticDateFormat

此外,在所有情况下,不要有 staticDateFormat

As noted by Jon Skeet, you can have both static and a shared instance variables in case you perform external synchronization (i.e. use synchronizedaround calls to the DateFormat)

正如 Jon Skeet 所指出的,如果您执行外部同步(即使用synchronized围绕调用DateFormat),您可以同时拥有静态和共享实例变量

回答by Jigar Joshi

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,则必须在外部进行同步。

This means suppose you have a object of DateFormat and you are accessing same object from two different threads and you are calling format method upon that object both thread will enter on the same method at the same time on the same object so you can visualize it won't result in proper result

这意味着假设您有一个 DateFormat 对象,并且您正在从两个不同的线程访问同一个对象,并且您正在对该对象调用 format 方法,两个线程将同时在同一个对象上以相同的方法进入,因此您可以想象它获胜不会产生正确的结果

If you have to work with DateFormat any how then you should do something

如果您必须使用 DateFormat 任何方式,那么您应该做一些事情

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

回答by seand

If there are multiple threads manipulating/accessing a single DateFormat instance and synchronization not used, it's possible to get scrambled results. That's because multiple non-atomic operations could be changing state or seeing memory inconsistently.

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

回答by Jon Skeet

I would expect data corruption - e.g. if you're parsing two dates at the same time, you could have one call polluted by data from another.

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

It's easy to imagine how this could happen: parsing often involves maintaining a certain amount of state as to what you've read so far. If two threads are both trampling on the same state, you'll get problems. For example, DateFormatexposes a calendarfield of type Calendar, and looking at the code of SimpleDateFormat, some methods call calendar.set(...)and others call calendar.get(...). This is clearly not thread-safe.

很容易想象这是如何发生的:解析通常涉及维护一定数量的状态,以了解您到目前为止所阅读的内容。如果两个线程都在同一个状态上践踏,你就会遇到问题。比如DateFormat暴露一个calendartype的字段Calendar,看代码SimpleDateFormat,有的方法调用,有的方法calendar.set(...)调用calendar.get(...)。这显然不是线程安全的。

I haven't looked into the exactdetails of why DateFormatisn't thread-safe, but for me it's enough to know that it isunsafe without synchronization - the exact manners of non-safety could even change between releases.

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

Personally I would use the parsers from Joda Timeinstead, as they arethread safe - and Joda Time is a much better date and time API to start with :)

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

回答by Micha? Niklas

Data is corrupted. Yesterday I noticed it in my multithread program where I had static DateFormatobject and called its format()for values read via JDBC. I had SQL select statement where I read the same date with different names (SELECT date_from, date_from AS date_from1 ...). Such statements were using in 5 threads for various dates in WHEREclasue. Dates looked "normal" but they differed in value -- while all dates were from the same year only month and day changed.

数据已损坏。昨天我在我的多线程程序中注意到了它,在那里我有静态DateFormat对象并format()通过 JDBC调用它的值。我有 SQL select 语句,其中我用不同的名称 ( SELECT date_from, date_from AS date_from1 ...)读取了相同的日期。这些语句在 5 个线程中用于WHEREclasue 的不同日期。日期看起来“正常”,但它们的价值不同——而所有日期都来自同一年,只是月份和日期发生了变化。

Others answers show you the way to avoid such corruption. I made my DateFormatnot static, now it is a member of a class that calls SQL statements. I tested also static version with synchronizing. Both worked well with no difference in performance.

其他答案向您展示了避免此类腐败的方法。我使我的DateFormat不是静态的,现在它是调用 SQL 语句的类的成员。我还通过同步测试了静态版本。两者都运行良好,性能没有差异。

回答by Buhake Sindi

The specifications of Format, NumberFormat, DateFormat, MessageFormat, etc. were not designed to be thread-safe. Also, the parse method calls on Calendar.clone()method and it affects calendar footprints so many threads parsing concurrently will change the cloning of the Calendar instance.

Format、NumberFormat、DateFormat、MessageFormat 等的规范不是为了线程安全而设计的。此外, parse 方法调用Calendar.clone()方法并影响日历足迹,因此许多线程同时解析将改变 Calendar 实例的克隆。

For more, these are bug reports such as thisand this, with results of DateFormat thread-safety issue.

有关更多信息,这些是诸如thisthis 之类的错误报告,以及 DateFormat 线程安全问题的结果。

回答by cjungel

If you are using Java 8 then you can use DateTimeFormatter.

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

A formatter created from a pattern can be used as many times as necessary, it is immutable and is thread-safe.

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

Code:

代码:

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

Output:

输出:

2017-04-17

回答by Erangad

This is my simple code that shows DateFormat is not thread safe.

这是我的简单代码,显示 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();
     }
}

Since all the threads are using the same SimpleDateFormat object, it throws the following exception.

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

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.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.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.run(DateTimeChecker.java:24)
at java.lang.Thread.run(Unknown Source)

But if we pass different objects to different threads, the code runs without errors.

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

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

These are the results.

这些是结果。

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

回答by Nux

In the best answer dogbane gave an example of using parsefunction and what it leads to. Below is a code that let's you check formatfunction.

在最佳答案中,dogbane 给出了使用parse函数及其导致的结果的示例。下面是一个让你检查format功能的代码。

Notice that if you change the number of executors (concurrent threads) you will get different results. From my experiments:

请注意,如果您更改执行程序(并发线程)的数量,您将获得不同的结果。从我的实验来看:

  • Leave newFixedThreadPoolset to 5 and the loop will fail every time.
  • Set to 1 and the loop will always work (obviously as all tasks are actually run one by one)
  • Set to 2 and the loop has only about 6% chance of working.
  • newFixedThreadPool将设置保留为 5,循环每次都会失败。
  • 设置为 1 并且循环将始终有效(显然因为所有任务实际上都是一一运行的)
  • 设置为 2 并且循环只有大约 6% 的工作机会。

I'm guessing YMMV depending on your processor.

我猜 YMMV 取决于您的处理器。

The formatfunction fails by formatting time from a different thread. This is because internally formatfunction is using calendarobject which is set up at the start of the formatfunction. And the calendarobject is a property of the SimpleDateFormatclass. Sigh...

format函数因格式化来自不同线程的时间而失败。这是因为内部format函数正在使用calendarformat函数开始时设置的对象。而calendar对象是SimpleDateFormat类的一个属性。叹...

/**
 * Test SimpleDateFormat.format (non) thread-safety.
 *
 * @throws Exception
 */
private static void testFormatterSafety() throws Exception {
    final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    final Calendar calendar1 = new GregorianCalendar(2013,1,28,13,24,56);
    final Calendar calendar2 = new GregorianCalendar(2014,1,28,13,24,56);
    String expected[] = {"2013-02-28 13:24:56", "2014-02-28 13:24:56"};

    Callable<String> task1 = new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "0#" + format.format(calendar1.getTime());
        }
    };
    Callable<String> task2 = new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "1#" + format.format(calendar2.getTime());
        }
    };

    //pool with X threads
    // note that using more then CPU-threads will not give you a performance boost
    ExecutorService exec = Executors.newFixedThreadPool(5);
    List<Future<String>> results = new ArrayList<>();

    //perform some date conversions
    for (int i = 0; i < 1000; i++) {
        results.add(exec.submit(task1));
        results.add(exec.submit(task2));
    }
    exec.shutdown();

    //look at the results
    for (Future<String> result : results) {
        String answer = result.get();
        String[] split = answer.split("#");
        Integer calendarNo = Integer.parseInt(split[0]);
        String formatted = split[1];
        if (!expected[calendarNo].equals(formatted)) {
            System.out.println("formatted: " + formatted);
            System.out.println("expected: " + expected[calendarNo]);
            System.out.println("answer: " + answer);
            throw new Exception("formatted != expected");
        /**
        } else {
            System.out.println("OK answer: " + answer);
        /**/
        }
    }
    System.out.println("OK: Loop finished");
}