[High Concurrency] Why is the SimpleDateFormat class not thread-safe? (with six solutions, recommended collection)

Hello everyone, I'm Glacier~~

First of all, let me ask everyone: Is the SimpleDateFormat class you use still safe? Why is SimpleDateFormat class not thread safe? Bring your questions to find answers in this article.

When it comes to the SimpleDateFormat class, no one who has done Java development will feel unfamiliar. Yes, it is the conversion class of date and time provided in Java. Here, why do you say that the SimpleDateFormat class has thread safety issues? Some friends may ask questions: We have been using the SimpleDateFormat class to parse and format date and time data in our production environment, and there has been no problem! My answer is: Yes, that's because your system doesn't have the amount of concurrency that the SimpleDateFormat class has problems with, which means your system isn't under load!

Next, let's take a look at why the SimpleDateFormat class has security problems under high concurrency, and how to solve the security problems of the SimpleDateFormat class.

Reproduce thread safety issue with SimpleDateFormat class

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

The specific usage, underlying principles and source code analysis of the CountDownLatch and Semaphore classes will be analyzed in depth later in [High Concurrency Topics]. Here, you only need to know that the CountDownLatch class can make a thread wait for other threads to execute each other before executing. The Semaphore class can be understood as a counting semaphore, which must be released by the thread that acquires it. It is often used to limit the number of threads accessing certain resources, such as current limiting.

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 测试SimpleDateFormat的线程不安全问题
 */
public class SimpleDateFormatTest01 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    private static final int THREAD_COUNT = 20;
    //SimpleDateFormat对象
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
    
    
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

It can be seen that in the SimpleDateFormatTest01 class, two constants are first defined, one is the total number of program executions, and the other is the number of threads running at the same time. The program combines thread pool, CountDownLatch class and Semaphore class to simulate high concurrency business scenarios. Among them, the code related to date conversion is only the following line.

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

When the program catches an exception, it prints the relevant information and exits the entire program. When the program runs correctly, it will print "All threads formatted date successfully".

The result information output by running the program is shown below.

Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" 线程:pool-1-thread-7 格式化日期失败
线程:pool-1-thread-9 格式化日期失败
线程:pool-1-thread-10 格式化日期失败
Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-5" Exception in thread "pool-1-thread-6" 线程:pool-1-thread-15 格式化日期失败
线程:pool-1-thread-21 格式化日期失败
Exception in thread "pool-1-thread-23" 线程:pool-1-thread-16 格式化日期失败
线程:pool-1-thread-11 格式化日期失败
java.lang.ArrayIndexOutOfBoundsException
线程:pool-1-thread-27 格式化日期失败
	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)线程:pool-1-thread-25 格式化日期失败

	at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
线程:pool-1-thread-14 格式化日期失败
	at java.text.DateFormat.parse(DateFormat.java:364)
	at io.binghe.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:47)
线程:pool-1-thread-13 格式化日期失败	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)
线程:pool-1-thread-20 格式化日期失败	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 that an exception was thrown when using the SimpleDateFormat class to format a date under high concurrency, and the SimpleDateFormat class is not thread-safe! ! !

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

Why is the SimpleDateFormat class not thread safe?

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

By looking at the source code of the SimpleDateFormat class, we know that: SimpleDateFormat inherits from the DateFormat class, and the DateFormat class 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 you can see from the comments, this Calendar object is used both for formatting and for parsing datetimes. Next, we look at the parse() method near the end.

@Override
public Date parse(String text, ParsePosition pos){
    
    
    ################此处省略N行代码##################
    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 obtained by calling the CalendarBuilder.establish() method, and the parameter of this method is exactly the previous Calendar object.

Next, let's take a look at the 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 the CalendarBuilder.establish() method, cal.clear() and cal.set() are called successively, that is, the value set in the cal object is cleared first, and then the new value is reset. Since there is no thread safety mechanism inside Calendar, and these two operations are not atomic, 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, knowing that the SimpleDateFormat class is not thread-safe, and the reason why the SimpleDateFormat class is not thread-safe, how to solve this problem? Next, let's discuss how to solve the thread safety problem of the 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 the SimpleDateFormat class in high concurrency scenarios. Here, a few commonly used methods are listed for reference. You can also give more solutions in the comment area.

1. Local variable method

The easiest way is to define the SimpleDateFormat class object as a local variable, as shown in the code below, define the SimpleDateFormat class object above 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 局部变量法解决SimpleDateFormat类的线程安全问题
 */
public class SimpleDateFormatTest02 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
    
    
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

At this point, run the modified program and the output is as follows.

所有线程格式化日期成功

As for why the use of local variables in high concurrency scenarios can solve the problem of thread safety, we will analyze in depth in the JVM memory model related content in [JVM Topics], and I won't introduce 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 method

Define the SimpleDateFormat class object as a global static variable. At this time, all threads share the SimpleDateFormat class object. At this time, when the method of formatting time is called, the SimpleDateFormat object can be synchronized. 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 通过Synchronized锁解决SimpleDateFormat类的线程安全问题
 */
public class SimpleDateFormatTest03 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    private static final int THREAD_COUNT = 20;
    //SimpleDateFormat对象
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
    
    
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

At this point, the key code to solve the problem is shown below.

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

Run the program and the output is as shown below.

所有线程格式化日期成功

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

3.Lock lock method

The lock method is the same as the synchronized lock method, both of which ensure the thread safety of the program through the JVM lock mechanism under high concurrency. The code to solve the problem by the Lock lock method 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;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author binghe
 * @version 1.0.0
 * @description 通过Lock锁解决SimpleDateFormat类的线程安全问题
 */
public class SimpleDateFormatTest04 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    private static final int THREAD_COUNT = 20;
    //SimpleDateFormat对象
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    //Lock对象
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
    
    
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }finally {
    
    
                        lock.unlock();
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

It can be known from the code that, first of all, a global static variable of type Lock is defined as the handle for locking and releasing the lock. Then lock with lock.lock() before the simpleDateFormat.parse(String) code. One thing to note here is that in order to prevent the program from throwing an exception and the lock cannot be released, 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 is as shown below.

所有线程格式化日期成功

This method will also affect the performance in high concurrency scenarios, and it is not recommended to use it in a high concurrency production environment.

4. ThreadLocal method

Using ThreadLocal to store a copy of the SimpleDateFormat object owned by each thread can effectively avoid thread safety problems caused by multithreading. The code to solve thread safety problems using ThreadLocal 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 通过ThreadLocal解决SimpleDateFormat类的线程安全问题
 */
public class SimpleDateFormatTest05 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
    
    
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

It can be known from the code that the copy of SimpleDateFormat used by each thread is saved in ThreadLocal, and each thread does not interfere with each other when using, thus solving the thread safety problem.

Run the program and the output is as shown below.

所有线程格式化日期成功

This method has high operating efficiency and is recommended for production environments with high concurrent business scenarios.

In addition, using ThreadLocal can also be written in the following form of code, 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 通过ThreadLocal解决SimpleDateFormat类的线程安全问题
 */
public class SimpleDateFormatTest06 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }catch (NumberFormatException e){
    
    
                        System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

5.DateTimeFormatter method

DateTimeFormatter is a class in the new date and time API provided by Java8. The DateTimeFormatter class is thread-safe and can be used directly to process date formatting operations 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 通过DateTimeFormatter类解决线程安全问题
 */
public class SimpleDateFormatTest07 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

It can be seen that the DateTimeFormatter class is thread-safe, and the DateTimeFormatter class can be used directly to process date formatting operations in high concurrency scenarios.

Run the program and the output is as shown below.

所有线程格式化日期成功

Using the DateTimeFormatter class to process date formatting operations is relatively efficient, and is recommended for use in production environments with high concurrency business scenarios .

6.joda-time method

joda-time is a third-party library that handles datetime formatting and is thread-safe. If you use joda-time to handle date and time formatting, you need to import a third-party library. Here, taking Maven as an example, the joda-time library is imported as follows.

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

After the introduction of the joda-time library, the implemented 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 通过DateTimeFormatter类解决线程安全问题
 */
public class SimpleDateFormatTest08 {
    
    
    //执行总次数
    private static final int EXECUTE_COUNT = 1000;
    //同时运行的线程数量
    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.currentThread().getName() + " 格式化日期失败");
                        e.printStackTrace();
                        System.exit(1);
                    }
                    semaphore.release();
                } catch (InterruptedException e) {
    
    
                    System.out.println("信号量发生错误");
                    e.printStackTrace();
                    System.exit(1);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("所有线程格式化日期成功");
    }
}

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

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

Run the program and the output is as shown below.

所有线程格式化日期成功

Using the joda-time library to process date formatting operations is relatively efficient, and is recommended for use in production environments with high concurrency business scenarios.

Summary of the solution to the thread safety problem of the SimpleDateFormat class

To sum up: In several solutions to solve the thread safety problem of the SimpleDateFormat class, the local variable method will create an object of the SimpleDateFormat class every time the thread executes the formatting time, which will lead to the creation of a large number of SimpleDateFormat objects, wasteful Running space and consuming server performance, because JVM creation and destruction of objects is performance-intensive. Therefore, it is not recommended to use it in a production environment with high concurrency requirements .

The synchronized lock method and the Lock lock method are essentially the same in dealing with the problem. By means of 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 use it in a production environment 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 when running, without interfering with each other, and has relatively high execution performance. It is recommended to be used in a high-concurrency production environment.

DateTimeFormatter is a class provided in Java 8 for processing date and time. The DateTimeFormatter class itself is thread-safe. After stress testing, the performance of DateTimeFormatter class processing date and time is not bad ( I will write a separate article on performance stress under high concurrency. test article ). Therefore, it is recommended to be used in a production environment in high concurrency scenarios.

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

Okay, let's stop here today, I'm Glacier, see you next time~~

Recommended reading:

Okay, let's stop here today, friends, like, favorite, comment, and start walking with one click, I'm Glacier, see you in the next issue~~

Guess you like

Origin blog.csdn.net/l1028386804/article/details/127486810