Why Is Java's Simpledateformat Not Thread-Safe

Making SimpleDateFormat thread safe

tl;dr

Rather than using strings, use java.time objects (LocalDate specifically) exchanged with your database via JDBC 4.2 or later.

myResultSet.getObject(      // Exchange modern java.time objects with your database.
"START_DATE" ,
LocalDate.class
) // Returns a `LocalDate` object.
.format( // Generate a `String` representing textually the content of this `LocalDate`.
DateTimeFormatter.ofPattern( "dd-MMM-uuuu" , Locale.US )
)

23-Jan-2018

Being immutable objects, the java.time objects are thread-safe by design. You can cache the java.time objects for use across threads.

java.time

Making SimpleDateFormat thread safe

Don’t.

Use the modern java.time classes that years ago supplanted the troublesome old legacy date-time classes such as SimpleDateFormat, java.util.Date, java.sql.Date, and Calendar.

The java.time classes are designed to be thread-safe. They use immutable objects pattern, to return fresh objects based on the values of an original rather than “mutating” (altering) the original.

Use smart objects, not dumb strings

I see no reason for using strings in your example code: Not in your database access code, not in your business object (Trade).

JDBC

As of JDBC 4.2, we can exchange java.time objects with the database. For a database column of a type akin to the SQL-standard DATE, use the class LocalDate. The LocalDate class represents a date-only value without time-of-day and without time zone.

myPreparedStatement.setObject( … , myLocalDate ) ;

Retrieval.

LocalDate myLocalDate = myResultSet.getObject( … , LocalDate.class ) ;

Business object

Your Trade class should be holding member variables startDate & endDate as LocalDate objects, not strings.

public class Trade {
private LocalDate startDate ;
private LocalDate endDate ;


// Getters
public LocalDate getStartDate() {
return this.startDate ;
}
public LocalDate getEndDate() {
return this.endDate;
}
public Period getPeriod() { // Number of years-months-days elapsed.
return Period.between( this.startDate , this.endDate ) ;
}

// Setters
public void setStartDate( LocalDate startDateArg ) {
this.startDate = startDateArg ;
}
public void setEndDate( LocalDate endDateArg ) {
this.endDate = endDateArg ;
}

@Override
public toString() {
"Trade={ " + "startDate=" + this.startDate.toString() …
}

}

No need for strings, no need for formatting patterns.

Strings

To exchange or store date-time values as text, use the standard ISO 8601 formats rather than a custom format as seen in your Question.

The java.time classes use the ISO 8601 formats by default when parsing/generating strings. So no need to specify a formatting pattern.

LocalDate ld = LocalDate.parse( "2018-01-23" ) ; // January 23, 2018.
String s = ld.toString() ; // Outputs 2018-01-23.

For presentation in a user-interface, let java.time automatically localize. To localize, specify:

  • FormatStyle to determine how long or abbreviated should the string be.
  • Locale to determine:

    • The human language for translation of name of day, name of month, and such.
    • The cultural norms deciding issues of abbreviation, capitalization, punctuation, separators, and such.

Example:

Locale l = Locale.CANADA_FRENCH ; 
DateTimeFormatter f =
DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL )
.withLocale( l ) ;
String output = ld.format( f ) ;

mardi 23 janvier 2018

The DateTimeFormatter class is thread-safe, by design, as an immutable object. You could hold one instance to be used across threads.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, Java SE 10, and later

    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

java.text.SimpleDateFormat not thread safe

Yes SimpleDateFormat is not thread safe and it is also recommended when you are parsing date it should access in synchronized manner.

public Date convertStringToDate(String dateString) throws ParseException {
Date result;
synchronized(df) {
result = df.parse(dateString);
}
return result;
}

one other way is on http://code.google.com/p/safe-simple-date-format/downloads/list

Java DateFormat is not threadsafe what does this leads to?

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.

Proving that SimpleDateFormat is not threadsafe

Lack of thread safety doesn't necessarily mean that the code will throw an exception. This was explained in Andy Grove's article, SimpleDateFormat and Thread Safety, which is no longer available online. In it, he showed SimpleDateFormat's lack of thread safety by showing that the output would not always be correct, given different inputs.

When I run this code, I get the following output:

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

Note that "01-Dec-2007" isn't even one of the strings in the test data. It is actually a combination of the dates being processed by the other two threads!

While the original article is no longer available online, the following code illustrates the issue. It was created based on articles that appeared to have been based on Andy Grove's initial article.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class SimpleDateFormatThreadSafety {
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);

public static void main(String[] args) {
new SimpleDateFormatThreadSafety().dateTest(List.of("01-Jan-1999", "14-Feb-2001", "31-Dec-2007"));
}

public void dateTest(List<String> testData) {
testData.stream()
.map(d -> new Thread(() -> repeatedlyParseAndFormat(d)))
.forEach(Thread::start);
}

private void repeatedlyParseAndFormat(String value) {
for (int i = 0; i < 1000; i++) {
Date d = tryParse(value);
String formatted = dateFormat.format(d);
if (!value.equals(formatted)) {
throw new RuntimeException("date conversion failed after " + i
+ " iterations. Expected " + value + " but got " + formatted);
}
}
}

private Date tryParse(String value) {
try {
return dateFormat.parse(value);
} catch (ParseException e) {
throw new RuntimeException("parse failed");
}
}
}

Sometimes this conversion fails by returning the wrong date, and sometimes it fails with a NumberFormatException:

java.lang.NumberFormatException: For input string: ".E2.31E2"

Why is Java's SimpleDateFormat class non thread safe?

Your answer pretty much lies here:

/* * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved

Prior to 1.4 hotspot writing multi-threaded code in Java was primarily a homework assignment for graduate students. The language had been around 10 years before the serious arrival of web-application servers and rise of the container-driven highly concurrent systems that most of us (non-android anyway) spend the vast majority of our time working in.

In 1996, Garbage Collection was a very slow and painful process that generally made your UI pause and look locked up while it happened, like wise creating new objects was considered very expensive (creating contiguous memory space when you're fighting with Windows 95 for a share of 4MB of physical memory with no L2 CPU cache takes some time.....).

So in an environment where multi-threading is extremely rare, and memory is at a premium (your average user is probably still on a 486 or Pentium 1 with 8MB or even 4MB of system memory....) it makes perfect sense to re-use a single instance of Calendar as much as possible, Calendar itself being something of a clunky beast.

We can scoff today at what a horrible practice it is for a class like that to be stateful, but it can also be easily defended as the right choice at the time.

Defending Sun's obsession with 100% backward compatibility and never updating it is another matter of course!

Confusion about thread safety - SimpleDateFormat example

SimpleDateFormat.parse() uses an instance variable called calendar to build the date from the string. If two threads try to parse at the same time, the calendar variable will get clobbered and you'll get wrong results.

Making the variable not static won't necessarily help, since two threads could still be using the same controller. A better solution is to either create a new DateFormat object each time you parse a date, or use thread local storage. Better still, use JodaTime which has thread safe parsers.

Thread safety issue with SimpleDateFormat

Your answer is correct. SimpleDateFormat isn't thread-safe that's true but each method call will create an own instance so this is ok. If the SimpleDateFormat were an instance variable this wouldn't be thread-safe (as you mentioned).



Related Topics



Leave a reply



Submit