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

hap*_*s10 140 java multithreading date-format

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

但是我无法想象出由此产生的实际问题.比如,我在类中有一个DateFormat字段,并且在多线程环境中的类(格式化日期)中的不同方法中使用相同的字段.

这会导致:

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

另外,请解释原因.

dog*_*ane 256

我们来试试吧.

这是一个程序,其中多个线程使用共享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());
    }
}
Run Code Online (Sandbox Code Playgroud)

运行几次,你会看到:

例外情况:

这里有一些例子:

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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

结果不正确:

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
Run Code Online (Sandbox Code Playgroud)

正确的结果:

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
Run Code Online (Sandbox Code Playgroud)

在多线程环境中安全使用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;
  }
}
Run Code Online (Sandbox Code Playgroud)

这是一篇包含更多细节的好文章.


Jon*_*eet 29

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

很容易想象这是如何发生的:解析通常涉及到目前为止你所阅读的内容保持一定的状态.如果两个线程都在相同的状态下践踏,那么你会遇到问题.例如,DateFormat公开一个calendar类型的字段Calendar,并查看SimpleDateFormat一些方法调用calendar.set(...)和其他调用的代码calendar.get(...).这显然不是线程安全的.

我没有仔细研究为什么不是线程安全的确切细节DateFormat,但对我而言,如果没有同步就知道它不安全的 - 非安全的确切方式甚至可能在版本之间发生变化.

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

  • +1 jodatime和声纳强制其使用:http://mestachs.wordpress.com/2012/03/17/dangerous-can-be-dating-in-java-joda-to-the-rescue/ (2认同)

cju*_*gel 14

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

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

码:

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

输出:

2017-04-17
Run Code Online (Sandbox Code Playgroud)


Boz*_*zho 10

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

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

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

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

    public void handleBar(Bar bar) {
        bar.setFormattedDate(df.format(bar.getStringDate());  
    }
}
Run Code Online (Sandbox Code Playgroud)

你应该使用:

public class Foo {

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

此外,在所有情况下,没有 static DateFormat

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

  • 因为它将中间计算存储在实例变量中,并且这不是线程安全的 (4认同)
  • 我根本没有看到.我没有使我的大多数类型都是线程安全的,所以我不希望它们的实例变量也是线程安全的.说你不应该在*static*变量中存储DateFormat更合理 - 或者如果你这样做,你需要同步. (2认同)