SimpleDateFormat thread is not safe

SimpleDateFormat threads are notoriously unsafe, and many of our friends have been fooled by them.

Here are the chestnuts in stackOverflow’s article Why-is-Javas-SimpleDateFormat-not-thread-safe.

public class ExampleClass {

	private static final Pattern dateCreateP = Pattern.compile("Д а т а п о д а discusses some related problems и : \ \ 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(a) { workConcurrently(); }}); }}public static void workConcurrently(a) {
		Matcher matcher = dateCreateP.matcher("Д а т а п о д а discusses some related problems и : 19:30:55 03.05.2015");
		Timestamp startAdvDate = null;
		try {
			if (matcher.find()) {
				String dateCreate = matcher.group(1);
				startAdvDate = newTimestamp(sdf.parse(dateCreate).getTime()); }}catch (Throwable th) {
			th.printStackTrace();
		}
		System.out.print("OK "); }}Copy the code

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)
Copy the code

The solution

  1. Every time new (instantiate) SimpleDateFormat.

  2. Use ThreadLocal to ensure that each thread gets a separate SimpleDateFormat.

public class DateUtil {
	private static final ThreadLocal<SimpleDateFormat> local = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

	public static String format(Date date) {
		return local.get().format(date);
	}

	public static Date parse(String dateStr) throws ParseException {
		returnlocal.get().parse(dateStr); }}Copy the code
  1. commons-lang3In theFastDateFormat.
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${commons-lang3-version}</version>
</dependency>
Copy the code

Performance contest

How about performance, JMH to a source: github.com/lets-mica/m…

# JMH version: 1.21 # VM version: JDK 1.8.0_221, Java HotSpot(TM) 64-Bit Server VM 25.221-B11 Benchmark Mode Cnt Score Error Units newSimpleDateFormat THRPT 5 114072.841 ± 989.135 ops/s threadLocal THRPT 5 348207.331 ± 46014.175 ops/s fastDateFormat THRPT 5 434391.553 ± 7799.593 ops/sCopy the code

Result: fastDateFormat got the highest score. Of course you think that’s it?

Use Instant + DateTimeFormatter

In MICA 1.2.1 we use Instant to relay dates using DateTimeFormatter formatting.

public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());

public String format(Date date) {
	return DATETIME_FORMATTER.format(date.toInstant());
}
Copy the code

Note: DateTimeFormatter formatting Instant requires specifying the time zone.

JDK 8 pressure test results

# JMH version: 1.21 # VM version: JDK 1.8.0_221, Java HotSpot(TM) 64-Bit Server VM 25.221-b11 Benchmark Mode Cnt Score Error Units fastDateFormat THRPT 5 417338.980 56543.104 ops/s instantformat THRPT 5 371028.709 72059.917 ops/sCopy the code

JDK 11 pressure test results

# JMH version: 1.21 # VM version: JDK 11.0.4 OpenJDK 64-bit Server VM, 11.0.4+ 10-B304.69 Benchmark Mode Cnt Score Error Units fastDateFormat THRPT 5 384637.138 7402.690 ops/s instantformat THRPT 5 487482.436 12490.986 ops/sCopy the code

conclusion

Using DateTimeFormatter + Instant is close to FastDateFormat in java8 and commons-lang3, with higher JDK versions doing well. This is a good option if you are using a higher version of JDK or are considering upgrading to a higher version later.

Welcome to pay attention to our public number: Dream technology, wonderful content daily push.