专栏首页程序员小明记一次Date引起的线上BUG处理

记一次Date引起的线上BUG处理

背景

Java8以前,每每操控时间,我们经常使用的类库就是Date,并且会通过SimpleDateFormat类对时间进行格式化。你可知道?Date类是一个可变类,SimpleDateFormat类也是线程不安全的,因此在多线程的场景下执行格式化操作时,就会发生意想不到的情况。下面我们看一下使用DateSimpleDateFormat在多线程下可能发生的问题以及使用LocalDateTimeDateTimeFormatter的方法和优势。

问题来了

多线程环境下,使用DateSimpleDateFormat时,如果我们将它定义为一个静态变量使用,虽然会避免重复创建实例, 但是会出现个别线程获取时间失败的现象,我们通过代码模拟这个场景:

运行main方法,查看控制台会发现有个别线程会报java.lang.NumberFormatException异常。类似下图所示:

问题分析

接下来,我们通过查看源码进一步分析(多图预警),可以看到SimpleDateFormat是直接继承的DateFormat类:

并重写了parse()(字符串转日期)和 format()(日期转字符串)方法,因此我们重点从这两个方法来分析。

首先是SimpleDateFormatparse()方法,该方法中创建了一个CalendarBuilder对象,

再往下看,会看到CalendarBuilder使用establish方法将变量calendar设值到其属性中,

![image-20200420012213545](/Users/xin/Library/Application Support/typora-user-images/image-20200420012213545.png)

calendar是父类DateFormat类的共享变量,可以被多个线程访问到

因此当SimpleDateFormat声明为static时,线程并不安全,多个线程同时操作访问就会抛出异常。

同样地通过查看format(),我们发现format方法中有一行calendar.setTime(date);也是操作的该共享变量calendar,线程也是不安全的。

有趣的是,在DateFormat源码注释上作者也已经给出醒目的提示:

使用Google翻译过来就是

日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一种格式,则必须在外部同步该格式。

解决方案

小明有一句座右铭,方法总比问题多。我们来看几个小明认为不错的解决方案。

1、仅在需要用到的地方创建一个新的实例,就没有线程安全问题。

点评:加重了创建对象的负担,频繁地创建和销毁对象,消耗资源,效率较低。

2、通过synchronized解决线程安全问题;

点评:并发量大的时候会对性能有影响,容易造成线程阻塞。

3、通过ThreadLocal保证线程之间变量不共享

点评:ThreadLocal可以确保每个线程都可以得到单独的一个SimpleDateFormat的对象,那么自然也就不存在竞争问题了。就是有点大材小用。

以上就是小明能够提供的所有方案。

什么?都不满意?那我们来看一下2020年JDK8的解决方案。

使用LocalDateTime

Java8以后,我们有了新的选择,使用LocalDateTime时间类。首先,LocalDateTime本身是线程安全的,其对应的格式化工具类DateTimeFormatter也是线程安全的,不存在变量共享,每一个属性字段都用了final关键字修饰,因此每次操作后都是返回的copy对象。并且LocalDateTime类本身也有很多操作时间的API来替代传统的Calendar类。

基于Java8DateTimeFormatter的解决方案,我们对之前的代码进行改造,多线程环境下,运行代码,并未发现任何异常,稳定高效:

我们可以看到在DateTimeFormatter源码上作者也贴心的加注释说明,该类是不可变的,并且是线程安全的。

同理,这点我们也可以从LocalDateTime的官方源码中看出。

其他骚操作

为了让大家忘掉之前使用Calendar操作时间的笨拙,我们来切实感受一下LocalDateTime给实际开发中带来的便利:

代码地址:https://github.com/WhenCoding/coder-xiaoming

总结

综上,小明推荐小伙伴们使用JDK8的LocalDateTime系列来取代Date系列,这样做不仅能够保证线上项目平稳运行,而且通过其自带的API操作时间还能提高开发效率。最后,感谢周同学激情投稿!

< END >

本文分享自微信公众号 - 程序员小明(coderxinqiji),作者:程序员小明

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 记一次伪并发引起的bug

    许杨淼淼
  • 记一次线程等待引起的bug解决过程

    使用Springboot的Scheduled注解框架执行定时任务,发现线上突然不打任何日志了,我们要找到它不打日志的原因

    老梁
  • 一个switch case引起的线上bug

    1、上午的时候,QA同学突然说,测试自动化的流程突然过不去了,问我是不是最近对线上做了某些修改。当时第一反应是不可能

    高性能架构探索
  • 记一次项目上线的BUG

    今天在写项目的时候突然项目经理跟我说之前项目的一个功能请求出现了异常,当时虎躯一震,上线的项目出问题了?什么问题?当时慌的一批。

    技术从心
  • 一次nginx引起的线上502故障

    今天突然接到某PM的求救,说微信支付到应用的请求一直返回502,于是初步了解完情况后,就进入了问题排查阶段。

    天策
  • 记一次线上CPU过高的问题以及处理方案

    本人所在的项目是一个支付项目,有个场景就是当用户下单之后,需要及时的知道订单的支付状态,有的渠道回调比较慢,故在用户下单之后将订单信息放入redis,然后不断的...

    码农飞哥
  • 复盘一次线上问题的处理过程

    上周产品出现了一个线上 bug,我和一位同事临时通宵给做了善后处理,本来是有很清晰的处理思路,以及很熟练的处理方法,但是过程中还是出现了各种各样的问题,现做个简...

    sylan215
  • 解Bug之路-记一次线上请求偶尔变慢的排查

    最近解决了个比较棘手的问题,由于排查过程挺有意思,于是就以此为素材写出了本篇文章。

    呆呆
  • 一次排查线上接口偶发异常耗时引起的思考!

    这要从线上的一个接口偶发异常耗时说起,事情往往不是你想象的样子,尤其是在排查问题的时候,切忌有先入为主的的某些判断。

    WindWant
  • Git 游戏攻略(上篇)- 基础&高级&整理提交记录

    一个学习git的网站,本文便是攻略。看一个个对话框也怪费时间的,整理一篇出来,看得清晰点。

    张风捷特烈
  • 记录一次通过性能日志处理线上性能问题的过程

    在项目发展初期,可能由于数据量和用户访问量的原因,系统不会出现性能问题,但是随着项目发展,数据量发生具体变化,系统访问量也不断增多,此时对代码的优化就显得...

    田维常
  • Java Web技术经验总结(四)

    在这里,resultType指的是查询到的每条记录的类型,因此应该用java.lang.String。

    阿杜
  • 微盟OOM排查之旅

    作者:张远,腾讯CDB高级工程师;余成真,微盟DBA负责人 首发:「老叶茶馆」微信公众号 背景      微盟是中小企业云端商业及营销解决方案提供商,客户...

    腾讯数据库技术
  • 前端小微团队的Gitlab实践

    首先要说的是分支管理,分支管理是git工作流的基础,好的分支设计有助于规范开发流程,也是CI/CD的基础。

    Tusi
  • shell脚本中字符串的常见操作及"command not found"报错处理(附源码)

    简介 昨天在通过shell脚本实现一个功能的时候,由于对shell处理字符串的方法有些不熟悉导致花了不少时间也犯了很多错误,因此将昨日的一些错误记录下来,避免以...

    程序员十三
  • 一次线上服务器磁盘耗尽的问题排查处理

    发现很多文件是删除的状态,但是空间还未释放,应该是有进程还在使用这些文件,导致这些以被删除的文件一致被占用,无法释放磁盘空间。

    IT云清
  • 分析一次自动登录引起的风波,并提供对Cookie的处理方式

    Xiaolei123
  • 我给Apache顶级项目提了个Bug

    这篇文章记录了给 Apache 顶级项目 - 分库分表中间件 ShardingSphere 提交 Bug 的历程。

    why技术
  • 微信小程序开发BUG经验总结

    小程序开发越来越热,开发中遇到各种各样的bug,在此总结了一些比较容易掉进去的坑分享给大家。

    Fundebug

扫码关注云+社区

领取腾讯云代金券