还在用 SimpleDateFormat 做时间格式化?小心项目崩掉

还在用 SimpleDateFormat 做时间格式化?小心项目崩掉


SimpleDateFormat在多线程环境下存在线程安全问题。

1 SimpleDateFormat.parse() 方法的线程安全问题

1.1 错误示例

错误使用SimpleDateFormat.parse()的代码如下:

import java.text.SimpleDateFormat;public class SimpleDateFormatTest { private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); public static void main(String[] args) { /** * SimpleDateFormat线程不安全,没有保证线程安全(没有加锁)的情况下,禁止使用全局SimpleDateFormat,否则报错 NumberFormatException * * private static final SimpleDateFormat dateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); */ for (int i = 0; i { try { // 错误写法会导致线程安全问题 System.out.println(Thread.currentThread().getName() + “–” + SIMPLE_DATE_FORMAT.parse(“2020-06-01 11:35:00”)); } catch (Exception e) { e.printStackTrace(); } }, “Thread-” + i); thread.start(); } }}

报错:

1.2 非线程安全原因分析

查看源码中可以看到:SimpleDateFormat继承DateFormat类,SimpleDateFormat转换日期是通过继承自DateFormat类的Calendar对象来操作的,Calendar对象会被用来进行日期-时间计算,既被用于format方法也被用于parse方法。

SimpleDateFormat 的 parse(String source) 方法 会调用继承自父类的 DateFormat 的 parse(String source) 方法

DateFormat 的 parse(String source) 方法会调用SimpleDateFormat中重写的 parse(String text, ParsePosition pos) 方法,该方法中有个地方需要关注

SimpleDateFormat 中重写的 parse(String text, ParsePosition pos) 方法中调用了 establish(calendar) 这个方法:

该方法中调用了 Calendar 的 clear() 方法

可以发现整个过程中Calendar对象它并不是线程安全的,如果,a线程将calendar清空了,calendar 就没有新值了,恰好此时b线程刚好进入到parse方法用到了calendar对象,那就会产生线程安全问题了!

正常情况下:

非线程安全的流程:

1.3 解决方法

方法1:每个线程都new一个SimpleDateFormat

import java.text.SimpleDateFormat;public class SimpleDateFormatTest { public static void main(String[] args) { for (int i = 0; i { try { // 每个线程都new一个 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); System.out.println(Thread.currentThread().getName() + “–” + simpleDateFormat.parse(“2020-06-01 11:35:00”)); } catch (Exception e) { e.printStackTrace(); } }, “Thread-” + i); thread.start(); } }}

方式2:synchronized等方式加锁

public class SimpleDateFormatTest { private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); public static void main(String[] args) { for (int i = 0; i { try { synchronized (SIMPLE_DATE_FORMAT) { System.out.println(Thread.currentThread().getName() + “–” + SIMPLE_DATE_FORMAT.parse(“2020-06-01 11:35:00”)); } } catch (Exception e) { e.printStackTrace(); } }, “Thread-” + i); thread.start(); } }}

方式3:使用ThreadLocal 为每个线程创建一个独立变量

import java.text.DateFormat;import java.text.SimpleDateFormat;public class SimpleDateFormatTest { private static final ThreadLocal SAFE_SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”)); public static void main(String[] args) { for (int i = 0; i { try { System.out.println(Thread.currentThread().getName() + “–” + SAFE_SIMPLE_DATE_FORMAT.get().parse(“2020-06-01 11:35:00”)); } catch (Exception e) { e.printStackTrace(); } }, “Thread-” + i); thread.start(); } }}

ThreadLocal的详细使用细节见:

https://blog.csdn.net/QiuHaoqian/article/details/117077792

2 SimpleDateFormat.format() 方法的线程安全问题

2.1 错误示例

import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class SimpleDateFormatTest { // 时间格式化对象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“mm:ss”); public static void main(String[] args) throws InterruptedException { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 执行任务 threadPool.execute(new Runnable() { @Override public void run() { Date date = new Date(finalI * 1000); // 得到时间对象 formatAndPrint(date); // 执行时间格式化 } }); } threadPool.shutdown(); // 线程池执行完任务之后关闭 } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { String result = simpleDateFormat.format(date); // 执行格式化 System.out.println("时间:" + result); // 打印最终结果 }}

从上述结果可以看出,程序的打印结果竟然是有重复内容的,正确的情况应该是没有重复的时间才对。

2.2 非线程安全原因分析

为了找到问题所在,查看原因 SimpleDateFormat 中 format 方法的源码来排查一下问题,format 源码如下:

从上述源码可以看出,在执行任务 SimpleDateFormat.format() 方法时,会使用 calendar.setTime() 方法将输入的时间进行转换,那么我们想象一下这样的场景:

  • 线程 1 执行了 calendar.setTime(date) 方法,将用户输入的时间转换成了后面格式化时所需要的时间;
  • 线程 1 暂停执行,线程 2 得到 CPU 时间片开始执行;
  • 线程 2 执行了 calendar.setTime(date) 方法,对时间进行了修改;
  • 线程 2 暂停执行,线程 1 得出 CPU 时间的继续执行,因为线程 1 和线程 2 使用的是同一对象,而时间已经被线程 2 修改了,所以此时当前线程 1 继续执行的时候就会出现线程安全的问题了。

正常的情况下,程序的执行是这样的:

非线程安全的执行流程是这样的:

2.3 解决方法

同样有三种解决方法

方法1:每个线程都new一个SimpleDateFormat

public class SimpleDateFormatTest { public static void main(String[] args) throws InterruptedException { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 执行任务 threadPool.execute(new Runnable() { @Override public void run() { // 得到时间对象 Date date = new Date(finalI * 1000); // 执行时间格式化 formatAndPrint(date); } }); } // 线程池执行完任务之后关闭 threadPool.shutdown(); } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { String result = new SimpleDateFormat("mm:ss").format(date); // 执行格式化 System.out.println("时间:" + result); // 打印最终结果 }}

方式2:synchronized等方式加锁

所有的线程必须排队执行某些业务才行,这样无形中就降低了程序的运行效率了

public class SimpleDateFormatTest { // 时间格式化对象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“mm:ss”); public static void main(String[] args) throws InterruptedException { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 执行任务 threadPool.execute(new Runnable() { @Override public void run() { Date date = new Date(finalI * 1000); // 得到时间对象 formatAndPrint(date); // 执行时间格式化 } }); } // 线程池执行完任务之后关闭 threadPool.shutdown(); } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { // 执行格式化 String result = null; // 加锁 synchronized (SimpleDateFormatTest.class) { result = simpleDateFormat.format(date); } // 打印最终结果 System.out.println("时间:" + result); }}

方式3:使用ThreadLocal 为每个线程创建一个独立变量

public class SimpleDateFormatTest { // 创建 ThreadLocal 并设置默认值 private static ThreadLocal dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat(“mm:ss”)); public static void main(String[] args) { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1000)); // 执行任务 for (int i = 0; i { Date date = new Date(finalI * 1000); // 得到时间对象 formatAndPrint(date); // 执行时间格式化 }); } threadPool.shutdown(); // 线程池执行完任务之后关闭 } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { String result = dateFormatThreadLocal.get().format(date); // 执行格式化 System.out.println(“时间:” + result); // 打印最终结果 }}

文章来源:blog.csdn.net/QiuHaoqian/article/details/116594422

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
用户投稿
上一篇 2022年6月19日 18:09
下一篇 2022年6月19日 18:09

相关推荐

  • 分享4条发微商朋友圈的方法(微商朋友圈应该怎么发)

    对于微商朋友来说,朋友圈的重要性不言而喻了。 那么微商的朋友圈到底该怎么发呢? 为什么同样是经营一个朋友圈,有的微商看起来逼格满满,实际效果也不错;而有的却动都不动就被屏蔽甚至拉黑…

    2022年11月27日
  • 30个无加盟费的项目(茶颜悦色奶茶店加盟费多少)

    茶颜悦色又爆了,8月18日,茶颜悦色南京门店正式开业,开张不到半小时,门店就人满为患,消费者的购买热情十分高涨,而由于人流量过大造成拥堵,茶颜悦色也不得不暂停营业。 当然,这里面排…

    2022年11月27日
  • 怎么转行总结出成功转行的3个步骤

    01 前段时间,由麦可思研究院发布的《就业蓝皮书:2019年中国大学生就业报告》显示,2018大学毕业生半年内的离职率为33%,主动离职的主要原因是“个人发展空间不够”和“薪资福利…

    2022年11月26日
  • 净利润率越高越好吗(净利润率多少合适)

    一、持续增收不增利,平均净利润率首次跌入个位数 2021年,增收不增利依旧是行业主流。具体来看,大部分企业营业收入呈增长态势,E50企业平均同比增速达到17.3%,但是利润增速则明…

    2022年11月26日
  • 《宝可梦朱紫》梦特性怎么获得?隐藏特性获取方法推荐

    宝可梦朱紫里有很多宝可梦都是拥有梦特性会变强的宝可梦,很多玩家不知道梦特性怎么获得,下面就给大家带来宝可梦朱紫隐藏特性获取方法推荐,感兴趣的小伙伴一起来看看吧,希望能帮助到大家。 …

    2022年11月25日
  • 《宝可梦朱紫》奇鲁莉安怎么进化?奇鲁莉安进化方法分享

    宝可梦朱紫中的奇鲁莉安要怎么进化呢?很多玩家都不知道,下面就给大家带来宝可梦朱紫奇鲁莉安进化方法分享,感兴趣的小伙伴一起来看看吧,希望能帮助到大家。 奇鲁莉安进化方法分享 奇鲁莉安…

    2022年11月25日
  • 中国疫情为何突然严重了(大连疫情为何突然增加那么多)

    有在关注国内此次疫情的小伙伴应该注意到了,近期国内各地的疫情情况也都是比较严峻的。而对于进入十一月份后各地突然爆发的疫情,大家也都第一时间查询原因。中国疫情为何突然严重了?为什么国…

    2022年11月25日
  • 为什么北京疫情越来越严重(疫情为什么越控制越严重)

    有在关注北京疫情的小伙伴应该注意到了,这几天北京疫情的新增数据情况也开始越发的严重起来,大家对北京此次疫情发展情况也都不断分析。为什么北京疫情越来越严重?导致北京本轮疫情的原因是什…

    2022年11月25日
  • 5+3疫情防控从哪天开始算(辽宁疫情防控最新政策)

    最近有关国内各地的疫情大家也都有在持续关注,目前国内各地疫情隔离时间也根据二十条防控措施有了新的调整。那么,5+3疫情防控从哪天开始算?对于密接的5+3隔离时间计算大家还是比较关心…

    2022年11月25日
  • 蓝码怎么变绿码需要几天(蓝码怎么变绿码需要几天)

    大家都知道健康码的颜色有红码、绿码、黄码,近日湖南健康码上线“蓝码”,不少小伙伴发现自己健康码变蓝了,都想赶紧恢复绿码,那么蓝码怎么变绿码需要几天?下面小编为大家带来蓝码变绿码需要…

    2022年11月25日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息