Why is Java's SimpleDateFormat not thread-safe?

JavaThread SafetySimpledateformat

Java Problem Overview


Please tell with a code example why is SimpleDateFormat not threadsafe. What is the problem in this class? Is The problem with format function of SimpleDateFormat? Please give a code which demonstrates this fault in class.

FastDateFormat is threadsafe. Why? what is the difference b/w the SimpleDateFormat and FastDateFormat?

Please explain with a code which demonstrates this issue?

Java Solutions


Solution 1 - Java

SimpleDateFormat stores intermediate results in instance fields. So if one instance is used by two threads they can mess each other's results.

Looking at the source code reveals that there is a Calendar instance field, which is used by operations on DateFormat / SimpleDateFormat.

For example parse(..) calls calendar.clear() initially and then calendar.add(..). If another thread invokes parse(..) before the completion of the first invocation, it will clear the calendar, but the other invocation will expect it to be populated with intermediate results of the calculation.

One way to reuse date formats without trading thread-safety is to put them in a ThreadLocal - some libraries do that. That's if you need to use the same format multiple times within one thread. But in case you are using a servlet container (that has a thread pool), remember to clean the thread-local after you finish.

To be honest, I don't understand why they need the instance field, but that's the way it is. You can also use joda-time DateTimeFormat which is threadsafe.

Solution 2 - Java

SimpleDateFormat is a concrete class for formatting and parsing dates in a locale-sensitive manner.

From the JavaDoc,
> But 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.

To make the SimpleDateFormat class thread-safe, look at the following approaches :

  • Create a new SimpleDateFormat instance each time you need to use one. Although this is thread safe, it is the slowest possible approach.
  • Use synchronization. This is a bad idea because you should never choke-point your threads on a server.
  • Use a ThreadLocal. This is the fastest approach of the 3 (see http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html).

Solution 3 - Java

DateTimeFormatter in Java 8 is immutable and thread-safe alternative to SimpleDateFormat.

Solution 4 - Java

ThreadLocal + SimpleDateFormat = SimpleDateFormatThreadSafe

package com.foocoders.text;
 
import java.text.AttributedCharacterIterator;
import java.text.DateFormatSymbols;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
 
public class SimpleDateFormatThreadSafe extends SimpleDateFormat {
 
	private static final long serialVersionUID = 5448371898056188202L;
	ThreadLocal<SimpleDateFormat> localSimpleDateFormat;
 
	public SimpleDateFormatThreadSafe() {
		super();
		localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
			protected SimpleDateFormat initialValue() {
				return new SimpleDateFormat();
			}
		};
	}
 
	public SimpleDateFormatThreadSafe(final String pattern) {
		super(pattern);
		localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
			protected SimpleDateFormat initialValue() {
				return new SimpleDateFormat(pattern);
			}
		};
	}
 
	public SimpleDateFormatThreadSafe(final String pattern, final DateFormatSymbols formatSymbols) {
		super(pattern, formatSymbols);
		localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
			protected SimpleDateFormat initialValue() {
				return new SimpleDateFormat(pattern, formatSymbols);
			}
		};
	}
 
	public SimpleDateFormatThreadSafe(final String pattern, final Locale locale) {
		super(pattern, locale);
		localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
			protected SimpleDateFormat initialValue() {
				return new SimpleDateFormat(pattern, locale);
			}
		};
	}
 
	public Object parseObject(String source) throws ParseException {
		return localSimpleDateFormat.get().parseObject(source);
	}
 
	public String toString() {
		return localSimpleDateFormat.get().toString();
	}
 
	public Date parse(String source) throws ParseException {
		return localSimpleDateFormat.get().parse(source);
	}
 
	public Object parseObject(String source, ParsePosition pos) {
		return localSimpleDateFormat.get().parseObject(source, pos);
	}
 
	public void setCalendar(Calendar newCalendar) {
		localSimpleDateFormat.get().setCalendar(newCalendar);
	}
 
	public Calendar getCalendar() {
		return localSimpleDateFormat.get().getCalendar();
	}
 
	public void setNumberFormat(NumberFormat newNumberFormat) {
		localSimpleDateFormat.get().setNumberFormat(newNumberFormat);
	}
 
	public NumberFormat getNumberFormat() {
		return localSimpleDateFormat.get().getNumberFormat();
	}
 
	public void setTimeZone(TimeZone zone) {
		localSimpleDateFormat.get().setTimeZone(zone);
	}
 
	public TimeZone getTimeZone() {
		return localSimpleDateFormat.get().getTimeZone();
	}
 
	public void setLenient(boolean lenient) {
		localSimpleDateFormat.get().setLenient(lenient);
	}
 
	public boolean isLenient() {
		return localSimpleDateFormat.get().isLenient();
	}
 
	public void set2DigitYearStart(Date startDate) {
		localSimpleDateFormat.get().set2DigitYearStart(startDate);
	}
 
	public Date get2DigitYearStart() {
		return localSimpleDateFormat.get().get2DigitYearStart();
	}
 
	public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
		return localSimpleDateFormat.get().format(date, toAppendTo, pos);
	}
 
	public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
		return localSimpleDateFormat.get().formatToCharacterIterator(obj);
	}
 
	public Date parse(String text, ParsePosition pos) {
		return localSimpleDateFormat.get().parse(text, pos);
	}
 
	public String toPattern() {
		return localSimpleDateFormat.get().toPattern();
	}
 
	public String toLocalizedPattern() {
		return localSimpleDateFormat.get().toLocalizedPattern();
	}
 
	public void applyPattern(String pattern) {
		localSimpleDateFormat.get().applyPattern(pattern);
	}
 
	public void applyLocalizedPattern(String pattern) {
		localSimpleDateFormat.get().applyLocalizedPattern(pattern);
	}
 
	public DateFormatSymbols getDateFormatSymbols() {
		return localSimpleDateFormat.get().getDateFormatSymbols();
	}
 
	public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
		localSimpleDateFormat.get().setDateFormatSymbols(newFormatSymbols);
	}
 
	public Object clone() {
		return localSimpleDateFormat.get().clone();
	}
 
	public int hashCode() {
		return localSimpleDateFormat.get().hashCode();
	}
 
	public boolean equals(Object obj) {
		return localSimpleDateFormat.get().equals(obj);
	}
 
}

https://gist.github.com/pablomoretti/9748230

Solution 5 - Java

Release 3.2 of commons-lang will have FastDateParser class that is a thread-safe substitute of SimpleDateFormat for Gregorian calendar. See LANG-909 for more information.

Solution 6 - Java

Here is the example which results in a strange error. Even Google gives no results:

public class ExampleClass {

private static final Pattern dateCreateP = Pattern.compile("Дата подачи:\\s*(.+)");
private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy");

public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(100);
    while (true) {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                workConcurrently();
            }
        });
    }
}

public static void workConcurrently() {
    Matcher matcher = dateCreateP.matcher("Дата подачи: 19:30:55 03.05.2015");
    Timestamp startAdvDate = null;
    try {
        if (matcher.find()) {
            String dateCreate = matcher.group(1);
            startAdvDate = new Timestamp(sdf.parse(dateCreate).getTime());
        }
    } catch (Throwable th) {
        th.printStackTrace();
    }
    System.out.print("OK ");
}
}

And result :

OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK java.lang.NumberFormatException: For input string: ".201519E.2015192E2"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.nonscalper.webscraper.processor.av.ExampleClass.workConcurrently(ExampleClass.java:37)
at com.nonscalper.webscraper.processor.av.ExampleClass$1.run(ExampleClass.java:25)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

Solution 7 - Java

Here’s an example defines a SimpleDateFormat object as a static field. When two or more threads access “someMethod” concurrently with different dates, they can mess with each other’s results.

    public class SimpleDateFormatExample {
         private static final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

         public String someMethod(Date date) {
            return simpleFormat.format(date);
         }
    }

You can create a service like below and use jmeter to simulate concurrent users using the same SimpleDateFormat object formatting different dates and their results will be messed up.

public class FormattedTimeHandler extends AbstractHandler {

private static final String OUTPUT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
private static final String INPUT_TIME_FORMAT = "yyyy-MM-ddHH:mm:ss";
private static final SimpleDateFormat simpleFormat = new SimpleDateFormat(OUTPUT_TIME_FORMAT);
// apache commons lang3 FastDateFormat is threadsafe
private static final FastDateFormat fastFormat = FastDateFormat.getInstance(OUTPUT_TIME_FORMAT);

public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {

    response.setContentType("text/html;charset=utf-8");
    response.setStatus(HttpServletResponse.SC_OK);
    baseRequest.setHandled(true);

    final String inputTime = request.getParameter("time");
    Date date = LocalDateTime.parse(inputTime, DateTimeFormat.forPattern(INPUT_TIME_FORMAT)).toDate();

    final String method = request.getParameter("method");
    if ("SimpleDateFormat".equalsIgnoreCase(method)) {
        // use SimpleDateFormat as a static constant field, not thread safe
        response.getWriter().println(simpleFormat.format(date));
    } else if ("FastDateFormat".equalsIgnoreCase(method)) {
        // use apache commons lang3 FastDateFormat, thread safe
        response.getWriter().println(fastFormat.format(date));
    } else {
        // create new SimpleDateFormat instance when formatting date, thread safe
        response.getWriter().println(new SimpleDateFormat(OUTPUT_TIME_FORMAT).format(date));
    }
}

public static void main(String[] args) throws Exception {
    // embedded jetty configuration, running on port 8090. change it as needed.
    Server server = new Server(8090);
    server.setHandler(new FormattedTimeHandler());

    server.start();
    server.join();
}

}

The code and jmeter script can be downloaded here .

Solution 8 - Java

Here is a code example that proves the fault in the class. I've checked: the problem occurs when using parse and also when you are only using format.

Solution 9 - Java

If you want to use the same date format among multiple threads, declare it as a static and synchronize on the instance variable when using it...

static private SimpleDateFormat sdf = new SimpleDateFormat("....");

synchronized(sdf)
{
   // use the instance here to format a date
}


// The above makes it thread safe

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVivek SharmaView Question on Stackoverflow
Solution 1 - JavaBozhoView Answer on Stackoverflow
Solution 2 - JavaSaurabh GokhaleView Answer on Stackoverflow
Solution 3 - JavaTheKojuEffectView Answer on Stackoverflow
Solution 4 - JavaPablo MorettiView Answer on Stackoverflow
Solution 5 - Javadma_kView Answer on Stackoverflow
Solution 6 - Javauser2602807View Answer on Stackoverflow
Solution 7 - JavayluView Answer on Stackoverflow
Solution 8 - JavaHans-Peter StörrView Answer on Stackoverflow
Solution 9 - JavaRodney P. BarbatiView Answer on Stackoverflow