user689842
user689842

Reputation:

Proving that SimpleDateFormat is not threadsafe

I want to show to a colleague that SimpleDateFormat is not thread-safe through a simple JUnit test. The following class fails to make my point (reusing SimpleDateFormat in a multi-threaded environment) and I don't understand why. Can you spot what is preventing my use of SDF from throwing a runtime exception?

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";
        }
    }
}

Upvotes: 5

Views: 1636

Answers (3)

Claudiu
Claudiu

Reputation: 229461

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"

Upvotes: 13

lexicalscope
lexicalscope

Reputation: 7328

It is not thread safe because of this code in SimpleDateFormat (in 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);
    ....
}

Each call to format stores the date in a calendar member variable of the SimpleDateFormat, and then subsequently applies the formatting to the contents of the calendar variable (not the date parameter).

So, as each call to format happens the data for all currently running formats may change (depending on the coherence model of your architecture) the data in the calendar member variable that is used by every other thread.

So if you run multiple concurrent calls to format you may not get an exception, but each call may return a result derived from the date of one of the other calls to format - or a hybrid combination of data from many different calls to format.

Upvotes: 3

Bharat Sinha
Bharat Sinha

Reputation: 14363

Isn't this part from javadoc of SimpleDateFormatter has sufficent proof about it?

Synchronization

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

And the major observation of not being thread safe is to get unexpected results and not an exception.

Upvotes: 4

Related Questions