A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

背景
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给实际开发中带来的便利:

1 个回复

倒序浏览
更多讯息欢迎添加小优:DKA-2018
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马