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:
- " Practice brings true knowledge: Deciphering the architecture of the strongest seckill system in the entire network, not all seckills are seckills! ! 》
- " From zero to hundreds of millions of users, how do I optimize MySQL database step by step? (recommended collection)》
- " I used multi-threading to further optimize the massive data proofreading system under the billion-level traffic e-commerce business, and the performance has been improved by 200%! ! (The whole process is dry, it is recommended to collect)》
- " I used multi-threading to optimize the massive data proofreading system under the billion-level traffic e-commerce business, and the performance was directly improved by 200%! ! (The whole process is dry, it is recommended to collect)》
- " I summed up the best learning route for concurrent programming with 10 pictures! ! (recommended collection)》
- " A lock that is faster than read-write locks in high concurrency scenarios, I am completely convinced after reading it! ! (recommended collection)》
- " Summary of the most complete performance optimization of the entire network! ! (Glacier Hematemesis Arrangement, Recommended Collection)》
- " After three days of playing MyBatis, feel free to ask! ! (Glacier Hematemesis Arrangement, Recommended Collection)》
- " Advice to those who have just started working: If you want to enter a big factory, you must master these concurrent programming knowledge! Complete Learning Route! ! (recommended collection)》
- " Advice to those juniors and juniors who have just started working: If you want to enter a big factory, you must master these core skills! Complete Learning Route! ! (recommended collection)》
- " Advice to those who have just started working: The sooner you know the basics of these computers and operating systems, the better! The 10,000-character long text is too top! ! (recommended collection)》
- " I developed a national-level game suitable for all ages in three days, supports playing music, and now open the complete source code and comments (recommended collection)! ! 》
- " I am the most hard-core high-concurrency programming author on the entire network, and the most noteworthy blogger on CSDN, do you agree? (recommended collection)》
- " Five years after graduation, from a monthly salary of 3,000 to an annual salary of one million, what core skills have I mastered? (recommended collection)》
- " I hacked into the Wifi of the girl next door and found out. . . (The whole process of actual combat dry goods, it is recommended to collect) "
- " Don't try "Panda Burning Incense" lightly, no, I regret it! 》
- " Tomb-sweeping Day secretly trained "Panda Burning Incense", and my computer "dedicated" to panda! 》
- " 730,000 words bursting with new features of Java8, I don't believe you can read it! (recommended collection)》
- " What's it like to unplug your server during peak business hours? 》
- " Summary of the most complete Linux commands on the entire network! ! (The most complete in history, recommended collection) "
- " Wrote a tool in Python, perfectly cracked MySQL! ! (recommended collection)》
- " These three new indexes in MySQL 8 have directly let MySQL take off, you don't even know it! ! (recommended collection)》
- " After finishing the Spring source code, I open sourced this distributed cache framework! ! (recommended collection)》
- "The product of the 100 million-level traffic high concurrent spike system is "oversold", just because there are these two huge pits in the JDK synchronization container used! ! (The record of stepping on the pit, it is recommended to collect) "
- " Advise those who have just started working: If you want to learn concurrent programming well, you must pay attention to the pits of these concurrent containers! ! (recommended collection)》
- "The company's reporting tool is too difficult to use. I picked up an Excel tool in three days. The operation lady said it was very easy to use, and it is now open source! ! (recommended collection)》
- " Advice to those who have just started working: If you want to enter a big factory, these core skills of concurrent programming are what you must master! ! (recommended collection)》
- " Ali Interviewer: How does the high-concurrency and high-traffic spike system correctly solve the problem of oversold inventory? (recommended collection)》
- " Redis five data types and usage scenarios summary! ! (including complete actual combat cases, it is recommended to collect)》
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~~