Abstract: Let’s take a look at the security problems of SimpleDateFormat class in high concurrency, and how to solve the security problems of SimpleDateFormat class.
This document is shared with “Thread Safety Problems and Solutions of SimpleDateFormat” in Huawei cloud community.
First off: is your SimpleDateFormat class safe? Why is the SimpleDateFormat class not thread-safe? Look for answers from the text with questions.
Mention SimpleDateFormat class, must have done Java development children will not feel strange. Yes, it is the date-time conversion class provided in Java. Why does the SimpleDateFormat class have thread-safety issues here? We have been using the SimpleDateFormat class to parse and format date and time data in our production environment without any problems. My answer is: yes, that’s because your system is not up to the concurrency of the SimpleDateFormat class problem, which means your system is under no load!
Let’s take a look at why the SimpleDateFormat class has security issues under high concurrency, and how to fix them.
Recreate the thread safety issue of the SimpleDateFormat class
An easy way to reproduce the thread-safety problems of the SimpleDateFormat class is to use a thread pool in combination with the CountDownLatch class and Semaphore class in Java and packages.
Details of the CountDownLatch and Semaphore classes and their underlying principles and source code will be discussed in more detail later in high Concurrency topics. All you need to know here is that the CountDownLatch class enables a thread to wait for other threads to finish executing. The Semaphore class can be understood as a counting Semaphore that must be released by the thread that acquires it. It is often used to limit the number of threads that can access certain resources, such as stream limiting.
Okay, let’s look at the code that recreates 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 test SimpleDateFormat thread safety */ public class SimpleDateFormatTest01 { Private static final int EXECUTE_COUNT = 1000; Private static final int THREAD_COUNT = 20; Private static SimpleDateFormat SimpleDateFormat = new SimpleDateFormat(" YYYY-MM-DD "); 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() +" formatting date failed "); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){system.out.println (" Thread: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
As you can see, in the SimpleDateFormatTest01 class, we first define two constants, the total number of times the program is executed and the number of threads running at the same time. The program combines the thread pool with the CountDownLatch class and Semaphore class to simulate a high concurrency business scenario. There is only one line of code for date conversion.
simpleDateFormat.parse("2020-01-01");
Copy the code
When the program catches an exception, it prints the relevant information and exits the entire program. When the program runs correctly, “All thread formatting dates succeeded” is printed.
The output of 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" Pool-1-thread-7 Thread that fails to format the date: pool-1-thread-9 Thread that fails to format the date: Pool-1-thread-5 "Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-5" Exception in thread Exception in thread "pool-1-thread-23" thread: Exception in thread "pool-1-thread-23" thread: Exception in thread "pool-1-thread-23" thread: - the thread pool - 1-16 date formatted thread failure: 1 - thread pool - - 11 Java formatting date failure. Lang. ArrayIndexOutOfBoundsException thread: - the thread pool - 1-27 formatting date failure 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) threads: - the thread pool - 1-25 formatting date failure at Java. Text. DecimalFormat. Parse the at (DecimalFormat. Java: 2084) java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at Java. Text. SimpleDateFormat. Parse (1514). A SimpleDateFormat Java: thread: - the thread pool - 1-14 date formatted failure at Java. Text. The DateFormat, parse the at (DateFormat. Java: 364) IO. Binghe. Concurrent. Lab06. SimpleDateFormatTest01. Lambda $main $0 (SimpleDateFormatTest01. Java: 47) threads: - the thread pool - 1-13 date format failure at Java. Util. Concurrent. ThreadPoolExecutor. RunWorker ats (ThreadPoolExecutor. Java: 1149) 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. A NumberFormatException. ForInputString (65). A NumberFormatException Java: thread: Pool-1-thread-20 Failed to format the 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 1Copy the code
The SimpleDateFormat class is not thread-safe. The SimpleDateFormat class is not thread-safe.
Next, let’s look at why the SimpleDateFormat class is not thread-safe.
Why isn’t the SimpleDateFormat class thread-safe?
So, let’s take a look at the root cause of what really makes the SimpleDateFormat class thread unsafe.
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;
Copy the code
As you can see from the annotations, this Calendar object is used for both formatting and parsing dates and times. Next, we’ll look at the parse() method near the end.
@Override public Date parse(String text, ParsePosition pos) {# # # # # # # # # # # # # # # # is omitted N lines of 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; }Copy the code
Visible, the last of the return value is by calling CalendarBuilder. Establish () method, and the method of parameter is just in front of the 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; }Copy the code
In CalendarBuilder. Establish () method has called the case the clear () and CAL. Set (), that is, the value of the first clear CAL objects set, set the new value again. Since there is no thread-safe mechanism in Calendar, and neither operation is atomic, multiple threads working on a SimpleDateFormat at the same time can mess up CAL values. Similarly, the format() method has the same problem.
Thus, there is a fundamental reason why the SimpleDateFormat class is not thread-safe: Calendar objects in the DateFormat class are shared by multiple threads, and Calendar objects themselves do not support thread-safe.
So, given that the SimpleDateFormat class is not thread-safe and why it is not thread-safe, what can be done to fix this problem? Let’s take a look at how to address the thread-safety issues of the SimpleDateFormat class in high-concurrency scenarios.
There are several ways to address the thread-safety issue of the SimpleDateFormat class in high-concurrency scenarios. Here are a few common ways to address this issue **. 支那
1. Local variable method
The simplest way to do this is to define the SimpleDateFormat class object as a local variable, as shown below, above the Parse (String) method.
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 number of executions 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: "+ thread.currentThread ().getName() +" formatting date failed "); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){system.out.println (" Thread: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
Run the modified program, and the output is as follows.
All threads formatted the date successfullyCopy the code
How the use of local variables can solve thread safety problems in high-concurrency scenarios will be explored in more detail in the JVM Topic on JVM memory patterns than discussed here.
Of course, this approach is not recommended in real production environments because it creates a large number of SimpleDateFormat class objects under high concurrency, which affects program performance.
2. Synchronized lock mode
The SimpleDateFormat class object is defined as a global static variable, and the SimpleDateFormat class object is shared by all threads. In this case, the SimpleDateFormat class object can be synchronized when the formatting time method is called, as shown in the code 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 Address thread safety issues with SimpleDateFormat using Synchronized locks */ public class SimpleDateFormatTest03 {// Total number of executions private static final Int EXECUTE_COUNT = 1000; Private static final int THREAD_COUNT = 20; Private static SimpleDateFormat SimpleDateFormat = new SimpleDateFormat(" YYYY-MM-DD "); 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() +" formatting date failed "); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){system.out.println (" Thread: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
At this point, the key code to resolve the problem is shown below.
synchronized (simpleDateFormat){
simpleDateFormat.parse("2020-01-01");
}
Copy the code
Run the program, and the output is shown below.
All threads formatted the date successfullyCopy the code
It is important to note that while this approach addresses the thread-safety issues of the SimpleDateFormat class, synchronized locks are added to the SimpleDateFormat class during program execution. This results in only one thread executing the parse(String) method at a time. In this case, the performance of the program will be affected, and this method is not recommended in production environments with high concurrency requirements.
3. Lock Lock mode
Lock Lock and synchronized Lock are implemented in the same principle, under high concurrency through the LOCKING mechanism of JVM to ensure the thread safety of the program. The code for solving the problem by locking 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 /** * @author binghe * @version 1.0.0 * @description SimpleDateFormatTest04 {// Total number of executions private static final Int EXECUTE_COUNT = 1000; Private static final int THREAD_COUNT = 20; Private static SimpleDateFormat SimpleDateFormat = new SimpleDateFormat(" YYYY-MM-DD "); private static SimpleDateFormat SimpleDateFormat = new SimpleDateFormat(" YYYY-MM-DD "); 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() +" formatting date failed "); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){system.out.println (" Thread: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
First, a global static variable of type Lock is defined as a handle to Lock and release the Lock. Then lock with lock.lock() before simpleDateFormat.parse(String) code. One thing to note here: To prevent a program from throwing an exception that prevents the lock from being released, be sure to put the lock release operation ina finally code block, as shown below.
finally {
lock.unlock();
}
Copy the code
Run the program, and the output is shown below.
All threads formatted the date successfullyCopy the code
This mode also affects the performance in high-concurrency scenarios and is not recommended for production environments with high concurrency.
4. The ThreadLocal approach
Using ThreadLocal to store a copy of the SimpleDateFormat object owned by each thread can effectively avoid thread safety problems caused by multiple threads.
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 /** * @author binghe * @version 1.0.0 * @description SimpleDateFormatTest05 {// Total number of executions 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: "+ thread.currentThread ().getName() +" formatting date failed "); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){system.out.println (" Thread: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
As you can see from the code, keeping a copy of SimpleDateFormat used by each thread in a ThreadLocal allows each thread to use it without interfering with each other, thus solving the thread-safety problem.
Run the program, and the output is shown below.
All threads formatted the date successfullyCopy the code
This mode is efficient and recommended for production scenarios with high concurrency.
Alternatively, using ThreadLocal, you can write the following code with the same effect.
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 /** * @author binghe * @version 1.0.0 * @description SimpleDateFormatTest06 {// Total number of executions 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: "+ thread.currentThread ().getName() +" formatting date failed "); e.printStackTrace(); System.exit(1); }catch (NumberFormatException e){system.out.println (" Thread: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
5. DateTimeFormatter way
DateTimeFormatter is a class in the new datetime API provided by Java8. The DateTimeFormatter class is thread-safe and can be used directly 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 */ public class SimpleDateFormatTest07 {// Total number of executions 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: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
As you can see, the DateTimeFormatter class is thread-safe and can be used directly to handle date formatting in high-concurrency scenarios.
Run the program, and the output is shown below.
All threads formatted the date successfullyCopy the code
Using DateTimeFormatter to format dates is efficient and recommended in production environments with high concurrency.
6. Joda – time mode
Joda-time is a thread-safe library for third-party date-time formatting. If you use Joda-time to handle date and time formatting, you need to introduce a third-party class library. Here, using Maven as an example, the joda-time library is introduced as shown below.
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
Copy the code
With the introduction of the Joda-time library, the 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 */ public class SimpleDateFormatTest08 {// Total number of executions 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: "+ thread.currentThread ().getName() +" formatting date failed "); 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 formatted date successfully "); }}Copy the code
The DateTime class is part of the org.joda.time package, and both the DateTimeFormat class and the DateTimeFormatter class are part of 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;
Copy the code
Run the program, and the output is shown below.
All threads formatted the date successfullyCopy the code
Using the Joda-time library to format dates is efficient and recommended in production environments with high concurrent service scenarios.
Solution to the SimpleDateFormat class thread safety problem summary
To sum up: The local variable method creates objects of the SimpleDateFormat class every time a thread executes a formatting time. This results in the creation of a large number of SimpleDateFormat objects. Wasted running space and server performance, as creating and destroying objects by the JVM costs performance. Therefore, it is not recommended for production environments with high concurrency requirements.
Synchronized and Lock Lock are essentially the same in handling problems. By locking, only one thread can format the date and time at a time. This method reduces the creation of SimpleDateFormat objects, but it is not recommended for production environments with high concurrency requirements because synchronization locks degrade performance.
ThreadLocal saves a copy of the SimpleDateFormat class object of each thread. This allows each thread to use its bound SimpleDateFormat object when running without interfering with each other. ThreadLocal provides high performance and is recommended for production environments with high concurrency.
DateTimeFormatter is a class provided in Java 8 that handles dates and times. The DateTimeFormatter class is inherently thread-safe and performs well in date and time performance tests. Therefore, it is recommended to be used in production environments with high concurrency.
Joda-time is a third-party date and time processing library. It is thread safe and has passed the test of high concurrency. It is recommended to be used in production environments with high concurrency.
Click to follow, the first time to learn about Huawei cloud fresh technology ~