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

发表于:2020-4-24 10:58

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Coder小明    来源:博客园

  生活中,我们需要掌控自己的时间,减少加班,提高效率;日常开发中,我们需要操作时间API,保证效率、安全、稳定。现在都2020年了,了解如何在JDK8及以后的版本中更好地操控时间就很有必要,尤其是一次线上BUG的发生,让小明更是深有体会。
  背景
  在Java8以前,每每操控时间,我们经常使用的类库就是Date,并且会通过SimpleDateFormat类对时间进行格式化。你可知道?Date类是一个可变类,SimpleDateFormat类也是线程不安全的,因此在多线程的场景下执行格式化操作时,就会发生意想不到的情况。下面我们看一下使用Date、SimpleDateFormat在多线程下可能发生的问题以及使用LocalDateTime、DateTimeFormatter的方法和优势。
  问题来了
  多线程环境下,使用Date、SimpleDateFormat时,如果我们将它定义为一个静态变量使用,虽然会避免重复创建实例, 但是会出现个别线程获取时间失败的现象,我们通过代码模拟这个场景:
  运行main方法,查看控制台会发现有个别线程会报java.lang.NumberFormatException异常。类似下图所示:
  问题分析
  接下来,我们通过查看源码进一步分析(多图预警),可以看到SimpleDateFormat是直接继承的DateFormat类:
  并重写了parse()(字符串转日期)和 format()(日期转字符串)方法,因此我们重点从这两个方法来分析。
  首先是SimpleDateFormat的parse()方法,该方法中创建了一个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类。
  基于Java8的DateTimeFormatter的解决方案,我们对之前的代码进行改造,多线程环境下,运行代码,并未发现任何异常,稳定高效:
  我们可以看到在DateTimeFormatter源码上作者也贴心的加注释说明,该类是不可变的,并且是线程安全的。
  同理,这点我们也可以从LocalDateTime的官方源码中看出。
  其他骚操作
  为了让大家忘掉之前使用Calendar操作时间的笨拙,我们来切实感受一下LocalDateTime给实际开发中带来的便利:
  代码地址:https://github.com/WhenCoding/coder-xiaoming
  总结
  综上,小明推荐小伙伴们使用JDK8的LocalDateTime系列来取代Date系列,这样做不仅能够保证线上项目平稳运行,而且通过其自带的API还能操作时间,还能提高开发效率,今晚可以不加班!
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号