"Java Dateformat Is Not Threadsafe" What Does This Leads To

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.

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

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.

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"

Making DateFormat Threadsafe. What to use, synchronized or Thread local

Avoid the legacy date-time classes

The troublesome old date-time classes bundled with the earliest versions of Java have been supplanted by the java.time classes. The java.time classes are thread-safe and use immutable objects.

java.time

Replace your formatter and date types with java.time types to automatically get thread-safety.

Define your DateTimeFormatter globally if so desired. That class can automatically localize the string being generated, or you can specify a certain format.

  • Specify a FormatStyle to determine length of abbreviation.
  • Specify a Locale to determine (a) the human language for translation of name of day, name of month, and such, and (b) the cultural norms deciding issues of abbreviation, capitalization, punctuation, and such.
  • Specify a ZoneId for a time zone in which to adjust the moment.

The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds. The ZonedDateTime class adjusts an Instant into a particular time zone.

Your code, translated to java.time classes. In real work I would break this out into multiple lines, and would trap for exceptions.

private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( Locale.CANADA_FRENCH ) ;
private static final ZoneId ZONE_ID = ZoneId.of( "America/Montreal" );

public static final String eventTypeToDateTimeString(long timestamp)
{
return Instant.ofEpochMilli( timestamp ).atZone( ZONE_ID ).format( DATE_TIME_FORMATTER );
}

Do not track date-time as count-from-epoch

I do not advise passing around a long as a way of representing date-time values. Makes debugging and logging difficulty as a human cannot discern the meaning of the date-time value. Instead, pass around the java.time types such as Instant. Using the java.time types provides type-safety and makes your code more self-documenting.


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, Java SE 11, and later - 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.

Thread safe code for Simpledate

Yes, it is thread safe. You can test it as done in "Java DateFormat is not threadsafe" what does this leads to?. If performace is an issue I would recommend changing synchronization to ThreadLocal as in Making DateFormat Threadsafe. What to use, synchronized or Thread local.



Related Topics



Leave a reply



Submit