专栏首页程序员小明记一次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 条评论
登录 后参与评论

相关文章

  • 让人头大的各种锁,从这里让你思绪清晰

    说到了锁我们经常会联想到生活中的锁,在我们日常中我们经常会接触到锁。比如我们的手机锁,电脑锁,再比如我们生活中的门锁,这些都是锁。

    程序员小明
  • ThreadLocal解决了什么问题

    由于 ThreadLocal 支持范型,如 ThreadLocal< StringBuilder >,为表述方便,后文用 变量 代表 ThreadLocal 本...

    程序员小明
  • 消息中间件(三):Rocketmq如何支持分布式事务消息

    A(存在DB操作)、B(存在DB操作)两方需要保证分布式事务一致性,通过引入中间层MQ,A和MQ保持事务一致性(异常情况下通过MQ反查A接口实现check),B...

    程序员小明
  • python多线程开发

           通过threading.Thread创建一个线程对象,target是目标函数,name可以指定自己喜欢的名字,线程的启动需要借助start方法。线...

    py3study
  • java.util.concurrent 在shorturl项目中的应用

    问题:微博短链项目应用到哪些concurrent包中的类,类的用途是什么?场景是怎样?效果? java.util.concurrent.atomic.Atomi...

    财主刀刀
  • java多线程中的死锁、活锁、饥饿、无锁都是什么鬼?

    死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常地执行下去了。 死锁 死锁是多线程中最差的一种情...

    Java技术栈
  • 死磕 java线程系列之线程池深入解析——生命周期

    上一章我们一起重温了下线程的生命周期(六种状态还记得不?),但是你知不知道其实线程池也是有生命周期的呢?!

    彤哥
  • Java多线程面试问答

    线程是Java面试问题中的热门话题之一。在这里,我从面试的角度列出了大多数重要的Java多线程面试问题,但是您应该对Java线程有足够的知识来处理后续问题。

    淡定的蜗牛
  • 大牛带你分析源码,学会正确使用 Java 线程池

    在日常的开发工作当中,线程池往往承载着一个应用中最重要的业务逻辑,因此我们有必要更多地去关注线程池的执行情况,包括异常的处理和分析等。本文主要聚焦在如何正确使用...

    美的让人心动
  • 夯实Java基础系列17:一文搞懂Java多线程使用方式、实现原理以及常见面试题

    本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前,首先让我们来了解下在操作系统中进程和线程的区...

    Java技术江湖

扫码关注云+社区

领取腾讯云代金券