我想通过一个简单的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";
}
}
}
答案 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>
因此,如果你运行多个并发调用来格式化你可能没有得到异常,但是每个调用可能返回从其中一个调用的日期派生的结果格式化 - 或来自许多不同格式调用的数据的混合组合。