"Java DateFormat is not threadsafe" what does this leads to?
Everybody cautions regarding Java DateFormat not being thread safe and I understand the concept theoretically.
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.
Will this cause:
- any exception like format exception
- discrepancy in data
- any other issue?
Also, please explain why.
Solution 1:
Let's try it out.
Here is a program in which multiple threads use a shared 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.
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.
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.
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 ThreadLocal
variable to hold the DateFormat
object, 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:
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 post with more details.
Solution 2:
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, DateFormat
exposes a calendar
field 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.
I haven't looked into the exact details of why DateFormat
isn't thread-safe, but for me it's enough to know that it is unsafe without synchronization - the exact manners of non-safety could even change between releases.
Personally I would use the parsers from Joda Time instead, as they are thread safe - and Joda Time is a much better date and time API to start with :)
Solution 3:
If you are using Java 8 then you can use 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