user2018791
user2018791

Reputation: 1153

Multiple threads use the same SimpleDateFormat without ThreadLocal

I am trying to understand the need of using ThreadLocal. Lot of people mention ThreadLocal should be used to provide a Per-Thread SimpleDateFormat, but they do not mention how will a mangled SimpleDateFormat will be seen if ThreadLocal is not used. I try the following code, seems it is just fine, I don't see a mangled SimpleDateFormat.

import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadLocalTest {
  private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
  private static final Date TODAY = new Date();
  private static final String expected = "07/09/2016";
  public static void main(String[] args) {
    for (int i = 0; i < 10000; i++) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          for (int j = 0; j < 1000; j++) {
            String real = dateFormat.format(TODAY);
            if (!real.equals(expected)) {
              throw new RuntimeException("Mangled SimpleDateFormat");
            }
          }
        }
      }).start();
    }
  }
}

How can I produce a exception like NumberFormatException because I don't use a ThreadLocal ?

Upvotes: 2

Views: 1078

Answers (4)

GhostCat
GhostCat

Reputation: 140525

The crucial point is: the SimpleDateFormat implementation is not thread safe.

That doesn't mean that it will throw an exception. It is worse: maybe, occasionally the shared formatter will simply give you wrong output!

You know, if "multi-threading issues" would nicely throw exceptions at you ... people would be much less afraid of them. Because we would have a direct hint that something went wrong.

Instead, things go wrong - and unnoticed.

Suggestion: enhance your test to

  1. always format the same Date object
  2. check that the result of formatting that Date is as expected (for example by comparing it against the result of an initial, first formatting operation)

And of course: only print mismatches, so that notice when they happen. Or better: throw your own exception on mismatch!

EDIT: turns out that the "better" way to enforce inconsistencies is to not use formatting but parsing!

Finally, to address another comment: of course, inconsistencies can only occur for objects that are shared between multiple threads. When each thread has its own format object, than there is no sharing; thus no problem.

Upvotes: 3

clstrfsck
clstrfsck

Reputation: 14837

One of the ways that SimpleDateFormat is not thread safe is that it has an internal calendar field, which holds a Calendar object. Pretty much the first thing that SimpleDateFormat does before actually formatting the date is call this.calendar.setTime(theDateYouPassedIn), no synchronization or locks. I'm not sure if this is the only way, but it should be fairly straightforward to inspect the code.

So, one way to get SimpleDateFormat to fail is to use dates that will produce differing output in different threads. Here is an example:

public class NotThreadSafe
{
  private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");

  public static void main(String[] args) {
    Date dref = new Date();

    // Dates for yesterday and tomorrow
    Date[] ds = new Date[] {
        new Date(dref.getTime() - (24L * 60L * 60L * 1000L)),
        new Date(dref.getTime() + (24L * 60L * 60L * 1000L))
    };
    String[] refs = new String[ds.length];
    for (int i = 0; i < ds.length; ++i) {
      // How the corresponding ds[i] should be formatted
      refs[i] = dateFormat.format(ds[i]);
    }

    for (int i = 0; i < 100; i++) {
      // Every even numbered thread uses ds[0] and refs[0],
      // odd numbered threads use ds[1] and refs[1].
      int index = (i % 2);
      final Date d = ds[index];
      final String ref = refs[index];
      new Thread(new Runnable() {
        @Override
        public void run() {
          while (true) {
            String s = dateFormat.format(d);
            if (!ref.equals(s)) {
              throw new IllegalStateException("Expected: " + ref + ", got: " + s);
            }
          }
        }
      }).start();
    }
  }
}

As the comments show, every even numbered thread will format yesterday's date, and odd numbered threads will use tomorrows date.

If you run this, threads will pretty much immediately start committing suicide by throwing exceptions until such time as you have only a handful left, likely all formatting the same date.

Upvotes: 0

Liviu Stirb
Liviu Stirb

Reputation: 6075

Date formats are not thread safe.

I think if you format the same day you can't reproduce, you should use 2 different dates or format also second and dates that have different seconds etc. Date format uses a Calendar under the hood on which it sets the date. If the first thread sets a date and start formatting the string and another thread with a different date comes and sets it on the same calendar, you will get wrong output.

Following code produces an exception/error:

    final Date today = new Date();
    String expectedToday = dateFormat.format(today);

    Date yesterday = new Date(today.getTime() - TimeUnit.DAYS.toMillis(1));
    String expectedYesterday = dateFormat.format(yesterday);

    for (int i = 0; i < 2; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    String format = dateFormat.format(today);
                    if (!expectedToday.equals(format)) {
                        System.out.println("error: " + format + " " + expectedToday);//Throw exception if you want
                    }

                    format = dateFormat.format(yesterday);
                    if (!expectedYesterday.equals(format)) {
                        System.out.println("error: " + format + " " + expectedYesterday);//Throw exception if you want
                    }
                }
            }
        }).start();
    }

Upvotes: 0

huangjingscnc
huangjingscnc

Reputation: 91

Just run these code, you will get "java.lang.NumberFormatException". If not occur, run a few more times

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class ThreadLocalDemo1 implements Runnable {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static void main(String[] args) {
    ThreadLocalDemo1 td = new ThreadLocalDemo1();
    Thread t1 = new Thread(td, "Thread-1");
    Thread t2 = new Thread(td, "Thread-2");
    t1.start();
    t2.start();
}

@Override
public void run() {
    for (int i = 0; i < 100; i++) {

        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + simpleDateFormat.toPattern());

        try {
            System.out.println("Formatted date is " + simpleDateFormat.parse("2013-05-24 06:02:20"));
        } catch (ParseException pe) {
            pe.printStackTrace();
        }

        System.out.println("=========================================================");
    }
}

}

Upvotes: 0

Related Questions