Abstract: We know that SimpleDateFormat is thread-safe, and this article describes a variety of solutions to keep it thread-safe.

This article is shared from huawei cloud community “Java SimpleDateFormat thread security problem, Virtual bamboo teach you a variety of solutions”, author: xiao Virtual bamboo.

1, scene

Before java8, to format dates and times, you needed SimpleDateFormat.

But we know that SimpleDateFormat is not thread-safe, so you have to handle it with care, lock it or not define it as static, new the object inside the method, and then format it. It is very troublesome, and repeatedly new objects, also increased memory overhead.

2. Why are SimpleDateFormat threads thread unsafe?

SimpleDateFormat: SimpleDateFormat

// Called from Format after creating a FieldDelegate private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // Convert input date to time field list calendar.setTime(date); . }Copy the code

The problem is with the member variable Calendar. If you define it static when using SimpleDateFormat, then SimpleDateFormat becomes a shared variable. The Calendar in SimpleDateFormat can then be accessed by multiple threads.

SimpleDateFormat’s parse method is also thread-unsafe:

public Date parse(String text, ParsePosition pos) { ... Date parsedDate; try { parsedDate = calb.establish(calendar).getTime(); // If the year value is ambiguous, // then the two-digit year == the default start year if (ambiguousYear[0]) { if (parsedDate.before(defaultCenturyStart))  { parsedDate = calb.addYear(100).establish(calendar).getTime(); } } } // An IllegalArgumentException will be thrown by Calendar.getTime() // if any fields are out of range, e.g., MONTH == 17. catch (IllegalArgumentException e) { pos.errorIndex = start; pos.index = oldStart; return null; } return parsedDate; }Copy the code

**parsedDate= calb.establish(calendar).getTime(); ** Gets the return value. The parameter of the calendar method is Calendar. Calendar can be accessed by multiple threads, causing thread insecurity.

Calb. Establish (Calendar)**

The calb.establish(Calendar) method calls cal.clear() and cal.set() first, and then sets the values. However, these two operations are not atomic, and there is no thread-safe mechanism to ensure that the value of CAL may cause problems in the case of multithreading concurrency.

2.1 Verify that SimpleDateFormat threads are unsafe

public class SimpleDateFormatDemoTest { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Public static void main (String [] args) {/ / 1, create a thread pool ExecutorService pool = Executors. NewFixedThreadPool (5); ThreadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3, disable thread pool pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { String dateString = simpleDateFormat.format(new Date()); try { Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(thread.currentThread ().getName()+" Thread safety: "+dateString.equals(dateString2)); } catch (Exception e) {system.out.println (thread.currentThread ().getName()+" formatting failed "); }}}}Copy the code

False occurs twice, indicating that the thread is unsafe. But also throw exceptions, this is serious.

3. Solutions

3.1 Solution 1: Do not define static variables, use local variables

To format or parse using SimpleDateFormat objects, define them as local variables. It’s thread safe.

Public class SimpleDateFormatDemoTest1 {public static void main (String [] args) {/ / 1, create a thread pool ExecutorService pool = Executors.newFixedThreadPool(5); ThreadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3, disable thread pool pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { SimpleDateFormat simpleDateFormat = new  SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = simpleDateFormat.format(new Date()); try { Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(thread.currentThread ().getName()+" Thread safety: "+dateString.equals(dateString2)); } catch (Exception e) {system.out.println (thread.currentThread ().getName()+" formatting failed "); }}}}Copy the code

As you can see from the figure, thread-safety is ensured, but this scheme is not recommended in high-concurrency scenarios because a large number of SimpleDateFormat objects will be created, affecting performance.

3.2 Solution 2: Synchronized and Lock

3.2.1 and synchronized lock

The SimpleDateFormat object is defined as a global variable, and synchronized is used to ensure thread-safety when SimpleDateFormat needs to be called to format the time.

public class SimpleDateFormatDemoTest2 { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Public static void main (String [] args) {/ / 1, create a thread pool ExecutorService pool = Executors. NewFixedThreadPool (5); ThreadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3, disable thread pool pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { synchronized (simpleDateFormat){ String dateString = simpleDateFormat.format(new Date()); Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(thread.currentThread ().getName()+" Thread safety: "+dateString.equals(dateString2)); }} Catch (Exception e) {system.out.println (thread.currentThread ().getName()+" formatting failed "); }}}}Copy the code

As shown, threads are safe. The global variable SimpleDateFormat is defined, reducing the cost of creating a large number of SimpleDateFormat objects. However, with synchronized locks, only one thread can execute the locked code block at a time, which can affect performance in high concurrency situations. However, this scheme is not recommended for high concurrency scenarios

3.2.2 the Lock locks

The principle of Lock Lock is the same as that of synchronized Lock. Lock mechanism is used to ensure thread security.

public class SimpleDateFormatDemoTest3 { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static Lock lock = new ReentrantLock(); Public static void main (String [] args) {/ / 1, create a thread pool ExecutorService pool = Executors. NewFixedThreadPool (5); ThreadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3, disable thread pool pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { lock.lock(); String dateString = simpleDateFormat.format(new Date()); Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(thread.currentThread ().getName()+" Thread safety: "+dateString.equals(dateString2)); } catch (Exception e) {system.out.println (thread.currentThread ().getName()+" formatting failed "); }finally { lock.unlock(); }}}}Copy the code

Lock can also be used to ensure thread safety. Finally, you must release the lock. Add lock.unlock() to finally; To ensure lock release.

Performance is affected in the case of high concurrency. This scheme is not recommended in high concurrency scenarios

3.3 Solution 3: Use ThreadLocal

Use ThreadLocal to ensure that each thread has a copy of the SimpleDateFormat object. This keeps the thread safe.

public class SimpleDateFormatDemoTest4 { private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }}; Public static void main (String [] args) {/ / 1, create a thread pool ExecutorService pool = Executors. NewFixedThreadPool (5); ThreadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3, disable thread pool pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { String dateString = threadLocal.get().format(new Date()); Date parseDate = threadLocal.get().parse(dateString); String dateString2 = threadLocal.get().format(parseDate); System.out.println(thread.currentThread ().getName()+" Thread safety: "+dateString.equals(dateString2)); } catch (Exception e) {system.out.println (thread.currentThread ().getName()+" formatting failed "); }finally {// To avoid memory leaks, use the remove method threadlocal.remove (); }}}}Copy the code

Using ThreadLocal is thread-safe and efficient. Suitable for high concurrency scenarios.

3.4 Solution 4: Use DateTimeFormatter instead of SimpleDateFormat

Use DateTimeFormatter instead of SimpleDateFormat (DateTimeFormatter is thread-safe, Java 8+ supported)

DateTimeFormatter introduces portal: Swastika blog teaches you to understand Java source code date and time related usage

public class DateTimeFormatterDemoTest5 { private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); Public static void main (String [] args) {/ / 1, create a thread pool ExecutorService pool = Executors. NewFixedThreadPool (5); ThreadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3, disable thread pool pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { String dateString = dateTimeFormatter.format(LocalDateTime.now()); TemporalAccessor temporalAccessor = dateTimeFormatter.parse(dateString); String dateString2 = dateTimeFormatter.format(temporalAccessor); System.out.println(thread.currentThread ().getName()+" Thread safety: "+dateString.equals(dateString2)); } catch (Exception e) { e.printStackTrace(); System.out.println(thread.currentThread ().getName()+" formatting failed "); }}}}Copy the code

Using DateTimeFormatter is thread safe and efficient. Suitable for high concurrency scenarios.

3.5 Solution 5: Replace SimpleDateFormat with FastDateFormat

Replace SimpleDateFormat with FastDateFormat (FastDateFormat is thread-safe and supported by the Apache Commons Lang package, regardless of the Java version)

public class FastDateFormatDemo6 { private static FastDateFormat fastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd  HH:mm:ss"); Public static void main (String [] args) {/ / 1, create a thread pool ExecutorService pool = Executors. NewFixedThreadPool (5); ThreadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3, disable thread pool pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { String dateString = fastDateFormat.format(new Date()); Date parseDate = fastDateFormat.parse(dateString); String dateString2 = fastDateFormat.format(parseDate); System.out.println(thread.currentThread ().getName()+" Thread safety: "+dateString.equals(dateString2)); } catch (Exception e) { e.printStackTrace(); System.out.println(thread.currentThread ().getName()+" formatting failed "); }}}}Copy the code

Using FastDateFormat is thread safe and efficient. Suitable for high concurrency scenarios.

3.5.1 FastDateFormat source code analysis

Apache Commons Lang 3.5 //FastDateFormat @override public String format(final Date Date) {return printer.format(Date); } @Override public String format(final Date date) { final Calendar c = Calendar.getInstance(timeZone, locale); c.setTime(date); return applyRulesToString(c); }Copy the code

Source code Calender is created in the format method, certainly will not appear setTime thread safety issues. This solves the thread safety puzzle. There are performance issues to consider, right?

So how do we get FastDateFormat

FastDateFormat.getInstance();
FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN);
Copy the code

Take a look at the corresponding source

/** * get FastDateFormat instance, * * @return FastDateFormat */ public static FastDateFormat getInstance() {return cache.getInstance (); } /** * get FastDateFormat instance, Use the default region < br > * * * support cache @ param pattern using {@ link Java. Text. SimpleDateFormat} the same date format * @ return FastDateFormat * @ throws */ public static FastDateFormat getInstance(final String Pattern) {return CACHE.getInstance(pattern, null, null); }Copy the code

So here we have a CACHE, so it looks like we’re using a CACHE, so let’s go down

private static final FormatCache<FastDateFormat> CACHE = new FormatCache<FastDateFormat>(){ @Override protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) { return new FastDateFormat(pattern, timeZone, locale); }}; // abstract class FormatCache<F extends Format> { ... private final ConcurrentMap<Tuple, F> cInstanceCache = new ConcurrentHashMap<>(7); private static final ConcurrentMap<Tuple, String> C_DATE_TIME_INSTANCE_CACHE = new ConcurrentHashMap<>(7); . }Copy the code

ConcurrentMap is added to the getInstance method to improve performance. And we know that ConcurrentMap is also thread-safe.

3.5.2 practice

/** * dateformat {@link FastDateFormat} : yyyy-MM */ public static final FastDateFormat NORM_MONTH_FORMAT = FastDateFormat.getInstance(NORM_MONTH_PATTERN);Copy the code

//FastDateFormat
public static FastDateFormat getInstance(final String pattern) {
   return CACHE.getInstance(pattern, null, null);
}
Copy the code

As can be seen in the figure, ConcurrentMap is used for caching. And the key is the format, and the time zone and locale are the same key.

4, the conclusion

1. Don’t define static variables, use local variables

2, synchronized Lock: synchronized Lock

3. Use ThreadLocal

4. Use DateTimeFormatter instead of SimpleDateFormat (DateTimeFormatter is thread-safe, Java 8+ support)

5. Replace SimpleDateFormat with FastDateFormat (FastDateFormat is thread-safe, supported by the Apache Commons Lang package, and recommended prior to Java8)

Click to follow, the first time to learn about Huawei cloud fresh technology ~