ToB企服应用市场:ToB评测及商务社交产业平台

标题: Simple Date Format类到底为啥不是线程安全的? [打印本页]

作者: 熊熊出没    时间: 2023-6-5 12:33
标题: Simple Date Format类到底为啥不是线程安全的?
摘要:我们就一起看下在高并发下Simple Date Format类为何会出现安全问题,以及如何解决Simple Date Format类的安全问题。
本文分享自华为云社区《【高并发】SimpleDateFormat类到底为啥不是线程安全的?》,作者:冰 河。
首先问下大家:你使用的Simple Date Format类还安全吗?为什么说Simple Date Format 类不是线程安全的?带着问题从本文中寻求答案。
提起Simple Date Format 类,想必做过Java开发的童鞋都不会感到陌生。没错,它就是Java中提供的日期时间的转化类。这里,为什么说Simple Date Format 类有线程安全问题呢?有些小伙伴可能会提出疑问:我们生产环境上一直在使用Simple Date Format 类来解析和格式化日期和时间类型的数据,一直都没有问题啊!我的回答是:没错,那是因为你们的系统达不到Simple Date Format 类出现问题的并发量,也就是说你们的系统没啥负载!
接下来,我们就一起看下在高并发下Simple Date Format 类为何会出现安全问题,以及如何解决 Simple Date Format 类的安全问题。
重现 Simple Date Format类的线程安全问题

为了重现Simple Date Format类的线程安全问题,一种比较简单的方式就是使用线程池结合Java并发包中的Count Down Latch类和Semaphore类来重现线程安全问题。
有关Count Down Latch 类和Semaphore类的具体用法和底层原理与源码解析在【高并发专题】后文会深度分析。这里,大家只需要知道 Count Down Latch 类可以使一个线程等待其他线程各自执行完毕后再执行。而Semaphore类可以理解为一个计数信号量,必须由获取它的线程释放,经常用来限制访问某些资源的线程数量,例如限流等。
好了,先来看下重现Simple Date Format 类的线程安全问题的代码,如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 测试SimpleDateFormat的线程不安全问题
  12. */
  13. public class SimpleDateFormatTest01 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. //SimpleDateFormat对象
  19. private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  20. public static void main(String[] args) throws InterruptedException {
  21. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  22. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  23. ExecutorService executorService = Executors.newCachedThreadPool();
  24. for (int i = 0; i < EXECUTE_COUNT; i++){
  25. executorService.execute(() -> {
  26. try {
  27. semaphore.acquire();
  28. try {
  29. simpleDateFormat.parse("2020-01-01");
  30. } catch (ParseException e) {
  31. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  32. e.printStackTrace();
  33. System.exit(1);
  34. }catch (NumberFormatException e){
  35. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  36. e.printStackTrace();
  37. System.exit(1);
  38. }
  39. semaphore.release();
  40. } catch (InterruptedException e) {
  41. System.out.println("信号量发生错误");
  42. e.printStackTrace();
  43. System.exit(1);
  44. }
  45. countDownLatch.countDown();
  46. });
  47. }
  48. countDownLatch.await();
  49. executorService.shutdown();
  50. System.out.println("package io.binghe.concurrent.lab06;
  51. import java.text.ParseException;
  52. import java.text.SimpleDateFormat;
  53. import java.util.concurrent.CountDownLatch;
  54. import java.util.concurrent.ExecutorService;
  55. import java.util.concurrent.Executors;
  56. import java.util.concurrent.Semaphore;
  57. /**
  58. * @author binghe
  59. * @version 1.0.0
  60. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  61. */
  62. public class SimpleDateFormatTest02 {
  63. //执行总次数
  64. private static final int EXECUTE_COUNT = 1000;
  65. //同时运行的线程数量
  66. private static final int THREAD_COUNT = 20;
  67. public static void main(String[] args) throws InterruptedException {
  68. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  69. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  70. ExecutorService executorService = Executors.newCachedThreadPool();
  71. for (int i = 0; i < EXECUTE_COUNT; i++){
  72. executorService.execute(() -> {
  73. try {
  74. semaphore.acquire();
  75. try {
  76. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  77. simpleDateFormat.parse("2020-01-01");
  78. } catch (ParseException e) {
  79. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  80. e.printStackTrace();
  81. System.exit(1);
  82. }catch (NumberFormatException e){
  83. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  84. e.printStackTrace();
  85. System.exit(1);
  86. }
  87. semaphore.release();
  88. } catch (InterruptedException e) {
  89. System.out.println("信号量发生错误");
  90. e.printStackTrace();
  91. System.exit(1);
  92. }
  93. countDownLatch.countDown();
  94. });
  95. }
  96. countDownLatch.await();
  97. executorService.shutdown();
  98. System.out.println("synchronized (simpleDateFormat){
  99. simpleDateFormat.parse("2020-01-01");
  100. }");
  101. }
  102. }");
  103. }
  104. }
复制代码
可以看到,在SimpleDateFormatTest01类中,首先定义了两个常量,一个是程序执行的总次数,一个是同时运行的线程数量。程序中结合线程池和Count Down Latch类与Semaphore类来模拟高并发的业务场景。其中,有关日期转化的代码只有如下一行。
  1. simpleDateFormat.parse("2020-01-01");
复制代码
当程序捕获到异常时,打印相关的信息,并退出整个程序的运行。当程序正确运行后,会打印“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("synchronized (simpleDateFormat){
simpleDateFormat.parse("2020-01-01");
}");
}
}”。
运行程序输出的结果信息如下所示。
  1. 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 格式化日期失败
  2. 线程:pool-1-thread-9 格式化日期失败
  3. 线程:pool-1-thread-10 格式化日期失败
  4. 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 格式化日期失败
  5. 线程:pool-1-thread-21 格式化日期失败
  6. Exception in thread "pool-1-thread-23" 线程:pool-1-thread-16 格式化日期失败
  7. 线程:pool-1-thread-11 格式化日期失败
  8. java.lang.ArrayIndexOutOfBoundsException
  9. 线程:pool-1-thread-27 格式化日期失败
  10. at java.lang.System.arraycopy(Native Method)
  11. at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:597)
  12. at java.lang.StringBuffer.append(StringBuffer.java:367)
  13. at java.text.DigitList.getLong(DigitList.java:191)线程:pool-1-thread-25 格式化日期失败
  14. at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
  15. at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
  16. at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
  17. 线程:pool-1-thread-14 格式化日期失败
  18. at java.text.DateFormat.parse(DateFormat.java:364)
  19. at io.binghe.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:47)
  20. 线程:pool-1-thread-13 格式化日期失败at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  21. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  22. at java.lang.Thread.run(Thread.java:748)
  23. java.lang.NumberFormatException: For input string: ""
  24. at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
  25. 线程:pool-1-thread-20 格式化日期失败at java.lang.Long.parseLong(Long.java:601)
  26. at java.lang.Long.parseLong(Long.java:631)
  27. at java.text.DigitList.getLong(DigitList.java:195)
  28. at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
  29. at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
  30. at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
  31. at java.text.DateFormat.parse(DateFormat.java:364)
  32. at io.binghe.concurrent.lab06.SimpleDateFormatTest01.lambda$main$0(SimpleDateFormatTest01.java:47)
  33. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  34. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  35. at java.lang.Thread.run(Thread.java:748)
  36. java.lang.NumberFormatException: For input string: ""
  37. at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
  38. at java.lang.Long.parseLong(Long.java:601)
  39. at java.lang.Long.parseLong(Long.java:631)
  40. at java.text.DigitList.getLong(DigitList.java:195)
  41. at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
  42. at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
  43. at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
  44. at java.text.DateFormat.parse(DateFormat.java:364)
  45. Process finished with exit code 1
复制代码
说明,在高并发下使用Simple Date Format 类格式化日期时抛出了异常,Simple Date Forma t类不是线程安全的!!!
接下来,我们就看下,Simple Date Format 类为何不是线程安全的。

Simple Date Format 类为何不是线程安全的?

那么,接下来,我们就一起来看看真正引起Simple Date Format类线程不安全的根本原因。
通过查看Simple Date Format类的源码,我们得知:Simple Date Format是继承自Date Format类,Date Format类中维护了一个全局的Calendar变量,如下所示。
  1. /**
  2.   * The {@link Calendar} instance used for calculating the date-time fields
  3.   * and the instant of time. This field is used for both formatting and
  4.   * parsing.
  5.   *
  6.   * <p>Subclasses should initialize this field to a {@link Calendar}
  7.   * appropriate for the {@link Locale} associated with this
  8.   * DateFormat.
  9.   * @serial
  10.   */
  11. protected Calendar calendar;
复制代码
从注释可以看出,这个Calendar对象既用于格式化也用于解析日期时间。接下来,我们再查看parse()方法接近最后的部分。
  1. @Override
  2. public Date parse(String text, ParsePosition pos){
  3.     ################此处省略N行代码##################
  4. Date parsedDate;
  5. try {
  6. parsedDate = calb.establish(calendar).getTime();
  7. // If the year value is ambiguous,
  8. // then the two-digit year == the default start year
  9. if (ambiguousYear[0]) {
  10. if (parsedDate.before(defaultCenturyStart)) {
  11. parsedDate = calb.addYear(100).establish(calendar).getTime();
  12. }
  13. }
  14. }
  15. // An IllegalArgumentException will be thrown by Calendar.getTime()
  16. // if any fields are out of range, e.g., MONTH == 17.
  17. catch (IllegalArgumentException e) {
  18. pos.errorIndex = start;
  19. pos.index = oldStart;
  20. return null;
  21. }
  22. return parsedDate;
  23. }
复制代码
可见,最后的返回值是通过调用CalendarBuilder.establish()方法获得的,而这个方法的参数正好就是前面的Calendar对象。
接下来,我们再来看看CalendarBuilder.establish()方法,如下所示。
  1. Calendar establish(Calendar cal) {
  2. boolean weekDate = isSet(WEEK_YEAR)
  3. && field[WEEK_YEAR] > field[YEAR];
  4. if (weekDate && !cal.isWeekDateSupported()) {
  5. // Use YEAR instead
  6. if (!isSet(YEAR)) {
  7. set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
  8. }
  9. weekDate = false;
  10. }
  11. cal.clear();
  12. // Set the fields from the min stamp to the max stamp so that
  13. // the field resolution works in the Calendar.
  14. for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
  15. for (int index = 0; index <= maxFieldIndex; index++) {
  16. if (field[index] == stamp) {
  17. cal.set(index, field[MAX_FIELD + index]);
  18. break;
  19. }
  20. }
  21. }
  22. if (weekDate) {
  23. int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
  24. int dayOfWeek = isSet(DAY_OF_WEEK) ?
  25. field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
  26. if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
  27. if (dayOfWeek >= 8) {
  28. dayOfWeek--;
  29. weekOfYear += dayOfWeek / 7;
  30. dayOfWeek = (dayOfWeek % 7) + 1;
  31. } else {
  32. while (dayOfWeek <= 0) {
  33. dayOfWeek += 7;
  34. weekOfYear--;
  35. }
  36. }
  37. dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
  38. }
  39. cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
  40. }
  41. return cal;
  42. }
复制代码
此时运行修改后的程序,输出结果如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }
复制代码
至于在高并发场景下使用局部变量为何能解决线程的安全问题,会在【JVM专题】的JVM内存模式相关内容中深入剖析,这里不做过多的介绍了。
当然,这种方式在高并发下会创建大量的SimpleDateFormat类对象,影响程序的性能,所以,这种方式在实际生产环境不太被推荐。
2.synchronized锁方式

将SimpleDateFormat类对象定义成全局静态变量,此时所有线程共享SimpleDateFormat类对象,此时在调用格式化时间的方法时,对SimpleDateFormat对象进行同步即可,代码如下所示。
  1. 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("package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }"); }}
复制代码
此时,解决问题的关键代码如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 通过Synchronized锁解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest03 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. //SimpleDateFormat对象
  19. private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  20. public static void main(String[] args) throws InterruptedException {
  21. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  22. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  23. ExecutorService executorService = Executors.newCachedThreadPool();
  24. for (int i = 0; i < EXECUTE_COUNT; i++){
  25. executorService.execute(() -> {
  26. try {
  27. semaphore.acquire();
  28. try {
  29. synchronized (simpleDateFormat){
  30. simpleDateFormat.parse("2020-01-01");
  31. }
  32. } catch (ParseException e) {
  33. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  34. e.printStackTrace();
  35. System.exit(1);
  36. }catch (NumberFormatException e){
  37. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  38. e.printStackTrace();
  39. System.exit(1);
  40. }
  41. semaphore.release();
  42. } catch (InterruptedException e) {
  43. System.out.println("信号量发生错误");
  44. e.printStackTrace();
  45. System.exit(1);
  46. }
  47. countDownLatch.countDown();
  48. });
  49. }
  50. countDownLatch.await();
  51. executorService.shutdown();
  52. System.out.println("synchronized (simpleDateFormat){
  53. simpleDateFormat.parse("2020-01-01");
  54. }");
  55. }
  56. }
复制代码
运行程序,输出结果如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }
复制代码
需要注意的是,虽然这种方式能够解决SimpleDateFormat类的线程安全问题,但是由于在程序的执行过程中,为SimpleDateFormat类对象加上了synchronized锁,导致同一时刻只能有一个线程执行parse(String)方法。此时,会影响程序的执行性能,在要求高并发的生产环境下,此种方式也是不太推荐使用的。
3.Lock锁方式

Lock锁方式与synchronized锁方式实现原理相同,都是在高并发下通过JVM的锁机制来保证程序的线程安全。通过Lock锁方式解决问题的代码如下所示。
  1. 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("package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }"); }}
复制代码
通过代码可以得知,首先,定义了一个Lock类型的全局静态变量作为加锁和释放锁的句柄。然后在simpleDateFormat.parse(String)代码之前通过lock.lock()加锁。这里需要注意的一点是:为防止程序抛出异常而导致锁不能被释放,一定要将释放锁的操作放到finally代码块中,如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. import java.util.concurrent.locks.Lock;
  9. import java.util.concurrent.locks.ReentrantLock;
  10. /**
  11. * @author binghe
  12. * @version 1.0.0
  13. * @description 通过Lock锁解决SimpleDateFormat类的线程安全问题
  14. */
  15. public class SimpleDateFormatTest04 {
  16. //执行总次数
  17. private static final int EXECUTE_COUNT = 1000;
  18. //同时运行的线程数量
  19. private static final int THREAD_COUNT = 20;
  20. //SimpleDateFormat对象
  21. private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  22. //Lock对象
  23. private static Lock lock = new ReentrantLock();
  24. public static void main(String[] args) throws InterruptedException {
  25. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  26. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  27. ExecutorService executorService = Executors.newCachedThreadPool();
  28. for (int i = 0; i < EXECUTE_COUNT; i++){
  29. executorService.execute(() -> {
  30. try {
  31. semaphore.acquire();
  32. try {
  33. lock.lock();
  34. simpleDateFormat.parse("2020-01-01");
  35. } catch (ParseException e) {
  36. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  37. e.printStackTrace();
  38. System.exit(1);
  39. }catch (NumberFormatException e){
  40. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }finally {
  44. lock.unlock();
  45. }
  46. semaphore.release();
  47. } catch (InterruptedException e) {
  48. System.out.println("信号量发生错误");
  49. e.printStackTrace();
  50. System.exit(1);
  51. }
  52. countDownLatch.countDown();
  53. });
  54. }
  55. countDownLatch.await();
  56. executorService.shutdown();
  57. System.out.println("finally {
  58. lock.unlock();
  59. }");
  60. }
  61. }
复制代码
运行程序,输出结果如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }
复制代码
此种方式同样会影响高并发场景下的性能,不太建议在高并发的生产环境使用。
4.ThreadLocal方式

使用ThreadLocal存储每个线程拥有的SimpleDateFormat对象的副本,能够有效的避免多线程造成的线程安全问题,使用ThreadLocal解决线程安全问题的代码如下所示。
  1. 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 threadLocal = new ThreadLocal(){ @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("package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }"); }}
复制代码
通过代码可以得知,将每个线程使用的SimpleDateFormat副本保存在ThreadLocal中,各个线程在使用时互不干扰,从而解决了线程安全问题。
运行程序,输出结果如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }
复制代码
此种方式运行效率比较高,推荐在高并发业务场景的生产环境使用。
另外,使用ThreadLocal也可以写成如下形式的代码,效果是一样的。
  1. 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 threadLocal = new ThreadLocal(); 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("package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }"); }}
复制代码
5.DateTimeFormatter方式

DateTimeFormatter是Java8提供的新的日期时间API中的类,DateTimeFormatter类是线程安全的,可以在高并发场景下直接使用DateTimeFormatter类来处理日期的格式化操作。代码如下所示。
  1. 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("package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }"); }}
复制代码
可以看到,DateTimeFormatter类是线程安全的,可以在高并发场景下直接使用DateTimeFormatter类来处理日期的格式化操作。
运行程序,输出结果如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }
复制代码
使用DateTimeFormatter类来处理日期的格式化操作运行效率比较高,推荐在高并发业务场景的生产环境使用。
6.joda-time方式

joda-time是第三方处理日期时间格式化的类库,是线程安全的。如果使用joda-time来处理日期和时间的格式化,则需要引入第三方类库。这里,以Maven为例,如下所示引入joda-time库。
  1. import org.joda.time.DateTime;
  2. import org.joda.time.format.DateTimeFormat;
  3. import org.joda.time.format.DateTimeFormatter;
复制代码
引入joda-time库后,实现的程序代码如下所示。
  1. package io.binghe.concurrent.lab06;package io.binghe.concurrent.lab06;
  2. import org.joda.time.DateTime;
  3. import org.joda.time.format.DateTimeFormat;
  4. import org.joda.time.format.DateTimeFormatter;
  5. import java.util.concurrent.CountDownLatch;
  6. import java.util.concurrent.ExecutorService;
  7. import java.util.concurrent.Executors;
  8. import java.util.concurrent.Semaphore;
  9. /**
  10. * @author binghe
  11. * @version 1.0.0
  12. * @description 通过DateTimeFormatter类解决线程安全问题
  13. */
  14. public class SimpleDateFormatTest08 {
  15. //执行总次数
  16. private static final int EXECUTE_COUNT = 1000;
  17. //同时运行的线程数量
  18. private static final int THREAD_COUNT = 20;
  19. private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");
  20. public static void main(String[] args) throws InterruptedException {
  21. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  22. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  23. ExecutorService executorService = Executors.newCachedThreadPool();
  24. for (int i = 0; i < EXECUTE_COUNT; i++){
  25. executorService.execute(() -> {
  26. try {
  27. semaphore.acquire();
  28. try {
  29. DateTime.parse("2020-01-01", dateTimeFormatter).toDate();
  30. }catch (Exception e){
  31. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  32. e.printStackTrace();
  33. System.exit(1);
  34. }
  35. semaphore.release();
  36. } catch (InterruptedException e) {
  37. System.out.println("信号量发生错误");
  38. e.printStackTrace();
  39. System.exit(1);
  40. }
  41. countDownLatch.countDown();
  42. });
  43. }
  44. countDownLatch.await();
  45. executorService.shutdown();
  46. System.out.println("import org.joda.time.DateTime;
  47. import org.joda.time.format.DateTimeFormat;
  48. import org.joda.time.format.DateTimeFormatter;");
  49. }
  50. }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("package io.binghe.concurrent.lab06;
  51. import java.text.ParseException;
  52. import java.text.SimpleDateFormat;
  53. import java.util.concurrent.CountDownLatch;
  54. import java.util.concurrent.ExecutorService;
  55. import java.util.concurrent.Executors;
  56. import java.util.concurrent.Semaphore;
  57. /**
  58. * @author binghe
  59. * @version 1.0.0
  60. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  61. */
  62. public class SimpleDateFormatTest02 {
  63. //执行总次数
  64. private static final int EXECUTE_COUNT = 1000;
  65. //同时运行的线程数量
  66. private static final int THREAD_COUNT = 20;
  67. public static void main(String[] args) throws InterruptedException {
  68. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  69. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  70. ExecutorService executorService = Executors.newCachedThreadPool();
  71. for (int i = 0; i < EXECUTE_COUNT; i++){
  72. executorService.execute(() -> {
  73. try {
  74. semaphore.acquire();
  75. try {
  76. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  77. simpleDateFormat.parse("2020-01-01");
  78. } catch (ParseException e) {
  79. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  80. e.printStackTrace();
  81. System.exit(1);
  82. }catch (NumberFormatException e){
  83. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  84. e.printStackTrace();
  85. System.exit(1);
  86. }
  87. semaphore.release();
  88. } catch (InterruptedException e) {
  89. System.out.println("信号量发生错误");
  90. e.printStackTrace();
  91. System.exit(1);
  92. }
  93. countDownLatch.countDown();
  94. });
  95. }
  96. countDownLatch.await();
  97. executorService.shutdown();
  98. System.out.println("synchronized (simpleDateFormat){
  99. simpleDateFormat.parse("2020-01-01");
  100. }");
  101. }
  102. }"); }}
复制代码
这里,需要注意的是:DateTime类是org.joda.time包下的类,DateTimeFormat类和DateTimeFormatter类都是org.joda.time.format包下的类,如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import org.joda.time.DateTime;
  3. import org.joda.time.format.DateTimeFormat;
  4. import org.joda.time.format.DateTimeFormatter;
  5. import java.util.concurrent.CountDownLatch;
  6. import java.util.concurrent.ExecutorService;
  7. import java.util.concurrent.Executors;
  8. import java.util.concurrent.Semaphore;
  9. /**
  10. * @author binghe
  11. * @version 1.0.0
  12. * @description 通过DateTimeFormatter类解决线程安全问题
  13. */
  14. public class SimpleDateFormatTest08 {
  15. //执行总次数
  16. private static final int EXECUTE_COUNT = 1000;
  17. //同时运行的线程数量
  18. private static final int THREAD_COUNT = 20;
  19. private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");
  20. public static void main(String[] args) throws InterruptedException {
  21. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  22. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  23. ExecutorService executorService = Executors.newCachedThreadPool();
  24. for (int i = 0; i < EXECUTE_COUNT; i++){
  25. executorService.execute(() -> {
  26. try {
  27. semaphore.acquire();
  28. try {
  29. DateTime.parse("2020-01-01", dateTimeFormatter).toDate();
  30. }catch (Exception e){
  31. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  32. e.printStackTrace();
  33. System.exit(1);
  34. }
  35. semaphore.release();
  36. } catch (InterruptedException e) {
  37. System.out.println("信号量发生错误");
  38. e.printStackTrace();
  39. System.exit(1);
  40. }
  41. countDownLatch.countDown();
  42. });
  43. }
  44. countDownLatch.await();
  45. executorService.shutdown();
  46. System.out.println("import org.joda.time.DateTime;
  47. import org.joda.time.format.DateTimeFormat;
  48. import org.joda.time.format.DateTimeFormatter;");
  49. }
  50. }
复制代码
运行程序,输出结果如下所示。
  1. package io.binghe.concurrent.lab06;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8. /**
  9. * @author binghe
  10. * @version 1.0.0
  11. * @description 局部变量法解决SimpleDateFormat类的线程安全问题
  12. */
  13. public class SimpleDateFormatTest02 {
  14. //执行总次数
  15. private static final int EXECUTE_COUNT = 1000;
  16. //同时运行的线程数量
  17. private static final int THREAD_COUNT = 20;
  18. public static void main(String[] args) throws InterruptedException {
  19. final Semaphore semaphore = new Semaphore(THREAD_COUNT);
  20. final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);
  21. ExecutorService executorService = Executors.newCachedThreadPool();
  22. for (int i = 0; i < EXECUTE_COUNT; i++){
  23. executorService.execute(() -> {
  24. try {
  25. semaphore.acquire();
  26. try {
  27. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  28. simpleDateFormat.parse("2020-01-01");
  29. } catch (ParseException e) {
  30. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  31. e.printStackTrace();
  32. System.exit(1);
  33. }catch (NumberFormatException e){
  34. System.out.println("线程:" + Thread.currentThread().getName() + " 格式化日期失败");
  35. e.printStackTrace();
  36. System.exit(1);
  37. }
  38. semaphore.release();
  39. } catch (InterruptedException e) {
  40. System.out.println("信号量发生错误");
  41. e.printStackTrace();
  42. System.exit(1);
  43. }
  44. countDownLatch.countDown();
  45. });
  46. }
  47. countDownLatch.await();
  48. executorService.shutdown();
  49. System.out.println("synchronized (simpleDateFormat){
  50. simpleDateFormat.parse("2020-01-01");
  51. }");
  52. }
  53. }
复制代码
使用joda-time库来处理日期的格式化操作运行效率比较高,推荐在高并发业务场景的生产环境使用。
解决SimpleDateFormat类的线程安全问题的方案总结

综上所示:在解决解决SimpleDateFormat类的线程安全问题的几种方案中,局部变量法由于线程每次执行格式化时间时,都会创建SimpleDateFormat类的对象,这会导致创建大量的SimpleDateFormat对象,浪费运行空间和消耗服务器的性能,因为JVM创建和销毁对象是要耗费性能的。所以,不推荐在高并发要求的生产环境使用。
synchronized锁方式和Lock锁方式在处理问题的本质上是一致的,通过加锁的方式,使同一时刻只能有一个线程执行格式化日期和时间的操作。这种方式虽然减少了SimpleDateFormat对象的创建,但是由于同步锁的存在,导致性能下降,所以,不推荐在高并发要求的生产环境使用。
ThreadLocal通过保存各个线程的SimpleDateFormat类对象的副本,使每个线程在运行时,各自使用自身绑定的SimpleDateFormat对象,互不干扰,执行性能比较高,推荐在高并发的生产环境使用。
DateTimeFormatter是Java 8中提供的处理日期和时间的类,DateTimeFormatter类本身就是线程安全的,经压测,DateTimeFormatter类处理日期和时间的性能效果还不错(后文单独写一篇关于高并发下性能压测的文章)。所以,推荐在高并发场景下的生产环境使用。
joda-time是第三方处理日期和时间的类库,线程安全,性能经过高并发的考验,推荐在高并发场景下的生产环境使用。
 
点击关注,第一时间了解华为云新鲜技术~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4