证明SimpleDateFormat不是线程安全的

时间:2012-09-21 15:16:03

标签: java multithreading

我想通过一个简单的JUnit测试向同事展示SimpleDateFormat 是线程安全的。下面的类没有说明我的观点(在多线程环境中重用SimpleDateFormat),我不明白为什么。你能发现什么阻止我使用SDF引发运行时异常吗?

public class SimpleDateFormatThreadTest
{
    @Test
    public void test_SimpleDateFormat_MultiThreaded() throws ParseException{
        Date aDate = (new SimpleDateFormat("dd/MM/yyyy").parse("31/12/1999"));
        DataFormatter callable = new DataFormatter(aDate);

        ExecutorService executor = Executors.newFixedThreadPool(1000);
        Collection<DataFormatter> callables = Collections.nCopies(1000, callable);

        try{
            List<Future<String>> futures = executor.invokeAll(callables);
            for (Future f : futures){
                try{
                    assertEquals("31/12/1999", (String) f.get());
                }
                catch (ExecutionException e){
                    e.printStackTrace();
                }
            }
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class DataFormatter implements Callable<String>{
    static SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

    Date date;

    DataFormatter(Date date){
        this.date = date;
    }

    @Override
    public String call() throws RuntimeException{
        try{
            return sdf.format(date);
        }
        catch (RuntimeException e){
            e.printStackTrace();
            return "EXCEPTION";
        }
    }
}

3 个答案:

答案 0 :(得分:11)

缺乏线程安全性并不一定意味着代码会抛出异常。试着看看安迪格罗夫的文章:SimpleDateFormat and Thread Safety;他表示,由于输入不同,输出并不总是正确的,因此显示其缺乏线程安全性。

  

当我运行此代码时,我得到以下输出:

java.lang.RuntimeException: date conversion failed after 3 iterations.
Expected 14-Feb-2001 but got 01-Dec-2007
  

请注意,“01-Dec-2007”甚至不是测试数据中的一个字符串。它实际上是其他两个线程正在处理的日期的组合!

答案 1 :(得分:4)

来自javadoc of SimpleDateFormatter的这部分是不是有足够的证明呢?

  

同步

     

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

不是线程安全的主要观察是获得意想不到的结果,而不是例外。

答案 2 :(得分:3)

由于SimpleDateFormat中的代码(在sun JVM 1.7.0_02中),它不是线程安全的:

private StringBuffer format(Date date, StringBuffer toAppendTo,
                            FieldDelegate delegate) {
    // Convert input date to time field list
    calendar.setTime(date);
    ....
}

每次格式调用都会将日期存储在calendar的{​​{1}}成员变量中,然后将格式应用于SimpleDateFormat变量的内容(而不是{{1}参数)。

因此,当每次调用格式化时,所有当前运行格式的数据都可能会更改(取决于您的体系结构的一致性模型)每个其他线程使用的calendar成员变量中的数据。 / p>

因此,如果你运行多个并发调用来格式化你可能没有得到异常,但是每个调用可能返回从其中一个调用的日期派生的结果格式化 - 或来自许多不同格式调用的数据的混合组合。