Why isn't the SimpleDateFormat class thread safe? (six solutions are attached, recommended Collection)

Posted by cac818 on Wed, 19 Jan 2022 19:30:59 +0100

Hello, I'm glacier~~

First of all, ask everyone: is the SimpleDateFormat class you use safe? Why is the SimpleDateFormat class not thread safe? Take questions and seek answers from this article.

When it comes to the SimpleDateFormat class, it must be familiar to children's shoes who have done java development. Yes, it is the date and time conversion class provided in Java. Here, why is the SimpleDateFormat class thread safe? Some small partners may ask questions: our production environment has been using the SimpleDateFormat class to parse and format data of date and time types. There has been no problem! My answer is: Yes, that's because your system can't reach the concurrency of the problem of SimpleDateFormat class, that is to say, your system has no load!

Next, let's take a look at why there are security problems with the SimpleDateFormat class and how to solve the security problems of the SimpleDateFormat class.

Reproduce the thread safety problem of the SimpleDateFormat class

In order to reproduce the thread safety problem of SimpleDateFormat class, a relatively simple way is to use thread pool in combination with CountDownLatch class and Semaphore class in Java concurrency package to reproduce the thread safety problem.

The specific usage, underlying principles and source code analysis of CountDownLatch class and semaphore class will be analyzed in depth later in [high concurrency topic]. Here, you only need to know that CountDownLatch class can make a thread wait for other threads to execute after their own execution. Semaphore class can be understood as a counting semaphore, which must be released by the thread that obtains it. It is often used to limit the number of threads accessing some resources, such as flow restriction.

Well, let's take a look at the code that reproduces the thread safety problem of the SimpleDateFormat class, as shown below.

package io.binghe.concurrent.lab06;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author binghe
 * @version 1.0.0
 * @description Thread insecurity in testing SimpleDateFormat
 */
public class SimpleDateFormatTest01 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;
    //SimpleDateFormat object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        simpleDateFormat.parse("2020-01-01");
                    } catch (ParseException e) {
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

As you can see, in the SimpleDateFormatTest01 class, two constants are first defined, one is the total number of times the program is executed, and the other is the number of threads running at the same time. The program combines the thread pool, CountDownLatch class and Semaphore class to simulate high concurrency business scenarios. There is only one line of code for date conversion.

simpleDateFormat.parse("2020-01-01");

When the program catches an exception, print relevant information and exit the operation of the whole program. When the program runs correctly, it will print "all threads format date successfully".

The result information output by the running program is as follows.

Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" Thread: pool-1-thread-7 Failed to format date
 Thread: pool-1-thread-9 Failed to format date
 Thread: pool-1-thread-10 Failed to format date
Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-5" Exception in thread "pool-1-thread-6" Thread: pool-1-thread-15 Failed to format date
 Thread: pool-1-thread-21 Failed to format date
Exception in thread "pool-1-thread-23" Thread: pool-1-thread-16 Failed to format date
 Thread: pool-1-thread-11 Failed to format date
java.lang.ArrayIndexOutOfBoundsException
 Thread: pool-1-thread-27 Failed to format date
	at java.lang.System.arraycopy(Native Method)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:597)
	at java.lang.StringBuffer.append(StringBuffer.java:367)
	at java.text.DigitList.getLong(DigitList.java:191)Thread: pool-1-thread-25 Failed to format date

	at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
Thread: pool-1-thread-14 Failed to format date
	at java.text.DateFormat.parse(DateFormat.java:364)
	at io.binghe.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:47)
Thread: pool-1-thread-13 Failed to format date	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

	at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
Thread: pool-1-thread-20 Failed to format date	at java.lang.Long.parseLong(Long.java:601)
	at java.lang.Long.parseLong(Long.java:631)

	at java.text.DigitList.getLong(DigitList.java:195)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at io.binghe.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:47)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Long.parseLong(Long.java:601)
	at java.lang.Long.parseLong(Long.java:631)
	at java.text.DigitList.getLong(DigitList.java:195)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)

Process finished with exit code 1

Note: an exception was thrown when using the SimpleDateFormat class to format the date under high parallel distribution. The SimpleDateFormat class is not thread safe!!!

Next, let's see why the SimpleDateFormat class is not thread safe.

Why isn't the SimpleDateFormat class thread safe?

Then, let's take a look at the root cause of thread insecurity of SimpleDateFormat class.

By looking at the source code of the SimpleDateFormat class, we know that SimpleDateFormat inherits from the DateFormat class, which maintains a global Calendar variable, as shown below.

/**
  * The {@link Calendar} instance used for calculating the date-time fields
  * and the instant of time. This field is used for both formatting and
  * parsing.
  *
  * <p>Subclasses should initialize this field to a {@link Calendar}
  * appropriate for the {@link Locale} associated with this
  * <code>DateFormat</code>.
  * @serial
  */
protected Calendar calendar;

As can be seen from the comments, this Calendar object is used for both formatting and parsing date and time. Next, let's look at the parse() method near the end.

@Override
public Date parse(String text, ParsePosition pos){
    ################Omitted here N Line code##################
    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;
}

It can be seen that the final return value is by calling calendarbuilder It is obtained by the establish () method, and the parameter of this method is exactly the previous Calendar object.

Next, let's take a look at calendarbuilder Establish () method, as shown below.

Calendar establish(Calendar cal) {
    boolean weekDate = isSet(WEEK_YEAR)
        && field[WEEK_YEAR] > field[YEAR];
    if (weekDate && !cal.isWeekDateSupported()) {
        // Use YEAR instead
        if (!isSet(YEAR)) {
            set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
        }
        weekDate = false;
    }

    cal.clear();
    // Set the fields from the min stamp to the max stamp so that
    // the field resolution works in the Calendar.
    for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
        for (int index = 0; index <= maxFieldIndex; index++) {
            if (field[index] == stamp) {
                cal.set(index, field[MAX_FIELD + index]);
                break;
            }
        }
    }

    if (weekDate) {
        int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
        int dayOfWeek = isSet(DAY_OF_WEEK) ?
            field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
        if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
            if (dayOfWeek >= 8) {
                dayOfWeek--;
                weekOfYear += dayOfWeek / 7;
                dayOfWeek = (dayOfWeek % 7) + 1;
            } else {
                while (dayOfWeek <= 0) {
                    dayOfWeek += 7;
                    weekOfYear--;
                }
            }
            dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
        }
        cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
    }
    return cal;
}

In calendarbuilder In the establish () method, cal. is called. Clear () and cal Set(), that is, first clear the value set in the cal object, and then reset the new value. There is no thread safety mechanism in Calendar, and neither of these two operations is atomic, so when multiple threads operate a SimpleDateFormat at the same time, the value of cal will be confused. Similarly, the format() method has the same problem.

Therefore, the fundamental reason why the SimpleDateFormat class is not thread safe is that the Calendar object in the DateFormat class is shared by multiple threads, and the Calendar object itself does not support thread safety.

So, after knowing that SimpleDateFormat class is not thread safe and the reason why SimpleDateFormat class is not thread safe, how to solve this problem? Next, let's discuss how to solve the thread safety problem of SimpleDateFormat class in high concurrency scenarios.

Solve the thread safety problem of SimpleDateFormat class

There are many ways to solve the thread safety problem of SimpleDateFormat class in high concurrency scenarios. Here are some common ways for reference. You can also give more solutions in the comment area.

1. Local variable method

The simplest way is to define the SimpleDateFormat class object as a local variable. In the following code, define the SimpleDateFormat class object on the parse(String) method to solve the problem.

package io.binghe.concurrent.lab06;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author binghe
 * @version 1.0.0
 * @description Local variable method to solve the thread safety problem of SimpleDateFormat class
 */
public class SimpleDateFormatTest02 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
                        simpleDateFormat.parse("2020-01-01");
                    } catch (ParseException e) {
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

At this time, run the modified program, and the output results are as follows.

All threads successfully formatted the date

As for why using local variables in high concurrency scenarios can solve thread safety problems, we will make an in-depth analysis in the relevant contents of JVM memory mode in [JVM topic], and we won't introduce it too much here.

Of course, this method will create a large number of SimpleDateFormat class objects under high concurrency, which will affect the performance of the program. Therefore, this method is not recommended in the actual production environment.

2.synchronized lock mode

Define the SimpleDateFormat class object as a global static variable. At this time, all threads share the SimpleDateFormat class object. At this time, when calling the method of formatting time, synchronize the SimpleDateFormat object. The code is as follows.

package io.binghe.concurrent.lab06;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author binghe
 * @version 1.0.0
 * @description Solve the thread safety problem of SimpleDateFormat class through Synchronized lock
 */
public class SimpleDateFormatTest03 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;
    //SimpleDateFormat object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        synchronized (simpleDateFormat){
                            simpleDateFormat.parse("2020-01-01");
                        }
                    } catch (ParseException e) {
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

At this point, the key code to solve the problem is as follows.

synchronized (simpleDateFormat){
	simpleDateFormat.parse("2020-01-01");
}

Run the program, and the output results are as follows.

All threads successfully formatted the date

It should be noted that although this method can solve the thread safety problem of SimpleDateFormat class, because the synchronized lock is added to the SimpleDateFormat class object during the execution of the program, only one thread can execute the parse(String) method at the same time. At this time, the execution performance of the program will be affected. This method is not recommended in the production environment requiring high concurrency.

3.Lock mode

The implementation principle of Lock lock mode is the same as that of synchronized Lock mode. Both of them ensure the thread safety of the program through the JVM Lock mechanism under high concurrency. The code to solve the problem through Lock is shown below.

package io.binghe.concurrent.lab06;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author binghe
 * @version 1.0.0
 * @description Solve the thread safety problem of SimpleDateFormat class through Lock lock
 */
public class SimpleDateFormatTest04 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;
    //SimpleDateFormat object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    //Lock object
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        lock.lock();
                        simpleDateFormat.parse("2020-01-01");
                    } catch (ParseException e) {
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }finally {
                        lock.unlock();
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

As can be seen from the code, first, a lock type global static variable is defined as the handle for locking and releasing the lock. Then in simpledateformat Parse (string) code passed lock Lock(). One thing to note here is: to prevent the lock from being released due to an exception thrown by the program, the operation of releasing the lock must be placed in the finally code block, as shown below.

finally {
	lock.unlock();
}

Run the program, and the output results are as follows.

All threads successfully formatted the date

This method will also affect the performance in high concurrency scenarios. It is not recommended to use it in high concurrency production environments.

4.ThreadLocal mode

Using ThreadLocal to store a copy of the SimpleDateFormat object owned by each thread can effectively avoid thread safety problems caused by multiple threads. The code for using ThreadLocal to solve thread safety problems is as follows.

package io.binghe.concurrent.lab06;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author binghe
 * @version 1.0.0
 * @description Solve the thread safety problem of SimpleDateFormat class through ThreadLocal
 */
public class SimpleDateFormatTest05 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;

    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        threadLocal.get().parse("2020-01-01");
                    } catch (ParseException e) {
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

From the code, we can know that the SimpleDateFormat copy used by each thread is saved in ThreadLocal, and each thread does not interfere with each other when using, so as to solve the thread safety problem.

Run the program, and the output results are as follows.

All threads successfully formatted the date

This method has high operation efficiency and is recommended to be used in the production environment of high concurrency business scenarios.

In addition, you can also write code in the following form using ThreadLocal, and the effect is the same.

package io.binghe.concurrent.lab06;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author binghe
 * @version 1.0.0
 * @description Solve the thread safety problem of SimpleDateFormat class through ThreadLocal
 */
public class SimpleDateFormatTest06 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;

    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();

    private static DateFormat getDateFormat(){
        DateFormat dateFormat = threadLocal.get();
        if(dateFormat == null){
            dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            threadLocal.set(dateFormat);
        }
        return dateFormat;
    }

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        getDateFormat().parse("2020-01-01");
                    } catch (ParseException e) {
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

5.DateTimeFormatter mode

DateTimeFormatter is a class in the new datetime API provided by Java 8. The DateTimeFormatter class is thread safe. You can directly use the DateTimeFormatter class to handle date formatting in high concurrency scenarios. The code is shown below.

package io.binghe.concurrent.lab06;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author binghe
 * @version 1.0.0
 * @description Solve thread safety problems through DateTimeFormatter class
 */
public class SimpleDateFormatTest07 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;

   private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        LocalDate.parse("2020-01-01", formatter);
                    }catch (Exception e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

You can see that the DateTimeFormatter class is thread safe. You can directly use the DateTimeFormatter class to handle date formatting in high concurrency scenarios.

Run the program, and the output results are as follows.

All threads successfully formatted the date

The DateTimeFormatter class is used to process date formatting operations, which is highly efficient. It is recommended to use it in the production environment of high concurrency business scenarios.

6. Joda time mode

Joda time is a third-party class library for processing date and time formatting, which is thread safe. If you use joda time to handle date and time formatting, you need to introduce a third-party class library. Here, take Maven as an example and introduce the joda time library as shown below.

<dependency>
	<groupId>joda-time</groupId>
	<artifactId>joda-time</artifactId>
	<version>2.9.9</version>
</dependency>

After introducing joda time library, the program code is as follows.

package io.binghe.concurrent.lab06;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * @author binghe
 * @version 1.0.0
 * @description Solve thread safety problems through DateTimeFormatter class
 */
public class SimpleDateFormatTest08 {
    //Total execution times
    private static final int EXECUTE_COUNT = 1000;
    //Number of threads running simultaneously
    private static final int THREAD_COUNT = 20;

    private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(THREAD_COUNT);
        final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < EXECUTE_COUNT; i++){
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    try {
                        DateTime.parse("2020-01-01", dateTimeFormatter).toDate();
                    }catch (Exception e){
                        System.out.println("Thread:" + Thread.currentThread().getName() + " Failed to format date");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
                    System.out.println("Semaphore error");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("All threads successfully formatted the date");
    }
}

Here, it should be noted that the DateTime class is org joda. The classes under the time package, DateTimeFormat class and DateTimeFormatter class are org joda. time. The classes under the format package are as follows.

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

Run the program, and the output results are as follows.

All threads successfully formatted the date

The joda time library is used to process the date formatting operation, which is highly efficient. It is recommended to use it in the production environment of high concurrency business scenarios.

Summary of solutions to thread safety problems of SimpleDateFormat class

To sum up: in several schemes to solve the thread safety problem of SimpleDateFormat class, the local variable method will create objects of SimpleDateFormat class every time the thread executes the formatting time, which will lead to the creation of a large number of SimpleDateFormat objects, waste operation space and consume the performance of the server, because the creation and destruction of objects by JVM will consume performance. Therefore, it is not recommended to use in production environments with high concurrency requirements.

The synchronized Lock mode and the Lock lock mode are essentially the same in dealing with problems. By locking, only one thread can perform the operation of formatting date and time at the same time. Although this method reduces the creation of SimpleDateFormat objects, the performance is degraded due to the existence of synchronization locks. Therefore, it is not recommended to be used in production environments with high concurrency requirements.

ThreadLocal saves a copy of the SimpleDateFormat class object of each thread, so that each thread uses its own bound SimpleDateFormat object at runtime, which does not interfere with each other and has high execution performance. It is recommended to be used in highly concurrent production environments.

DateTimeFormatter is a class for processing date and time provided in Java 8. The DateTimeFormatter class itself is thread safe. After pressure test, the performance effect of DateTimeFormatter class in processing date and time is good (a separate chapter on high performance and performance pressure test will be written later). Therefore, it is recommended to use in the production environment in the high concurrency scenario.

Joda time is a third-party class library for processing date and time. It is thread safe and its performance has passed the test of high concurrency. It is recommended to be used in production environments in high concurrency scenarios.

Write at the end

If you want to enter a big factory, want to be promoted and raised, or are confused about your existing work, you can communicate with me privately. I hope some of my experience can help you~~

Recommended reading:

Well, that's all for today. Let's praise, collect and comment. Let's walk up three times with one button. I'm glacier. I'll see you next time~~

Topics: Concurrent Programming