Abstract: 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.

This article is shared from the HUAWEI CLOUD community "Thread Safety Issues and Solutions of ", author: Glacier.

First ask everyone: Is the SimpleDateFormat class you use safe? Why is the SimpleDateFormat class not thread-safe? Find answers from this article with questions.

Speaking of the SimpleDateFormat class, you will not be unfamiliar with children's shoes who have done Java development. Yes, it is the date and time conversion class provided in Java. Here, why does the SimpleDateFormat class have thread safety issues? Some friends may ask questions: Our production environment has been using the SimpleDateFormat class to parse and format date and time data, and there has been no problem! My answer is: yes, it is because your system can't reach the concurrency of the SimpleDateFormat class, which means that your system has no 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 the thread safety issue of 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 concurrency package to reproduce the thread safety problem.

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

Well, let's first 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("所有线程格式化日期成功");
    }
}

As you can see, 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 the thread pool and the CountDownLatch class and the Semaphore class to simulate high-concurrency business scenarios. Among them, the code for date conversion only has 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 as follows.

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

Explain 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: SimpleDateFormat is inherited from the DateFormat class, and a global Calendar variable is maintained in the DateFormat class, 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){
    ################此处省略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 happens to be 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 Calendar does not have a thread safety mechanism, and these two operations are not atomic, the value of cal will be confused when multiple threads operate a SimpleDateFormat at the same time. Similarly, the format() method also has the same problem.

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

So, I learned that the SimpleDateFormat class is not thread-safe, and the reasons that cause the SimpleDateFormat class to be not thread-safe, then how to solve this problem? Next, we will discuss together how to solve the thread safety problem of SimpleDateFormat class in high concurrency scenarios.

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.

1. Local variable method

The simplest way is to define the SimpleDateFormat class object as a local variable, the code shown below, the SimpleDateFormat class object is defined 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("所有线程格式化日期成功");
    }
}

Run the modified program at this time, and the output result is as follows.

All threads format the date successfully
As for why the use of local variables in high concurrency scenarios can solve the thread safety problem, I will analyze it in depth in the JVM memory mode related content of [JVM topic], and I will not introduce too much here.

Of course, this method will create a large number of SimpleDateFormat objects under high concurrency, which will affect the performance of the program. Therefore, 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 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, the output result is as follows.

All threads format the date successfully
It should be noted that although this method can solve the thread safety problem of the SimpleDateFormat class, due to the synchronized lock 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, it will affect the execution performance of the program. In a production environment that requires high concurrency, is not recommended.

3.Lock mode

The Lock lock method has the same implementation principle as the synchronized lock method, and both ensure the thread safety of the program through the JVM lock mechanism under high concurrency. The code to solve the problem through the Lock method 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 通过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, a global static variable of the Lock type is defined as the handle for locking and releasing the lock. Then lock by lock.lock() before the simpleDateFormat.parse(String) code. One thing to note here is: 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, the output result is as follows.

All threads format the date successfully
This method will also affect the performance in high-concurrency scenarios. is not recommended for use in high-concurrency production environments.

4. ThreadLocal way

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 for using ThreadLocal to solve thread safety problems is shown below.

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 a copy of the SimpleDateFormat used by each thread is stored in ThreadLocal, and each thread does not interfere with each other during use, thereby solving the thread safety problem.

Run the program, the output result is as follows.

All threads format the date successfully
This method has relatively high operating efficiency. recommended to be used in a production environment with high concurrency 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 Java 8. The DateTimeFormatter class is thread-safe. You can directly use the DateTimeFormatter class 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("所有线程格式化日期成功");
    }
}

As you can see, the DateTimeFormatter class is thread-safe. You can directly use the DateTimeFormatter class to process date formatting operations in high concurrency scenarios.

Run the program, the output result is as follows.

All threads format the date successfully
Using the DateTimeFormatter class to process date formatting operations is more efficient. recommended for use in production environments with high concurrency business scenarios.

6.joda-time way

joda-time is a third-party library that handles date and time formatting and is thread-safe. If you use joda-time to process date and time formatting, you need to introduce a third-party library. Here, taking Maven as an example, 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 the joda-time library, the implemented program code is shown below.

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 both the DateTimeFormat class and the DateTimeFormatter class are 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, the output result is as follows.

All threads format the date successfully
Using the joda-time library to process date formatting operations is more efficient. recommended to be used in a production environment with high concurrency business scenarios.

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

In summary: among several solutions to solve the thread safety problem of the SimpleDateFormat class, the local variable method will create SimpleDateFormat objects every time the thread executes the formatting time, which will result in the creation of a large number of SimpleDateFormat objects and waste Run space and consume server performance, because JVM creation and destruction of objects is performance-consuming. Therefore, not recommended for use 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. Through the locking method, only one thread can perform the operation of formatting the 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, 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 SimpleDateFormat object when it is running, which does not interfere with each other and has high execution performance. recommended for use in a high-concurrency production environment.

DateTimeFormatter is a class for processing dates and times provided in Java 8. The DateTimeFormatter class itself is thread-safe. After pressure testing, the DateTimeFormatter class has a good performance effect on processing dates and times. Therefore, recommended to be used in a production environment under high concurrency scenarios.

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. recommended for use in a production environment under high concurrency scenarios.

Click to follow, and learn about the fresh technology of Huawei Cloud for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量