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开始:)
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
),你可以同时拥有静态变量和共享实例变量.