专栏名称: java那些事
分享java开发中常用的技术,分享软件开发中各种新技术的应用方法。每天推送java技术相关或者互联网相关文章。关注“java那些事”,让自己做一个潮流的java技术人!《java程序员由笨鸟到菜鸟》系列文章火热更新中。
目录
相关文章推荐
芋道源码  ·  Java 新魔法:利用 Function ... ·  14 小时前  
芋道源码  ·  300 秒到 4 秒,如何将 MySQL ... ·  昨天  
芋道源码  ·  Nginx 部署负载均衡服务全解析 ·  2 天前  
芋道源码  ·  MySQL 中 Varchar(50) 和 ... ·  3 天前  
芋道源码  ·  船新 IDEA 2025.1 要来了,新特性真香! ·  3 天前  
51好读  ›  专栏  ›  java那些事

还在使用SimpleDateFormat?你的项目崩没?

java那些事  · 公众号  · Java  · 2019-03-20 16:00

正文

作者:Felix zyf


一、前言


日常开发中,我们经常需要使用时间相关类,说到时间相关类,想必大家对 SimpleDateFormat 并不陌生。主要是用它进行时间的 格式化输出和解析 ,挺方便快捷的,但是 SimpleDateFormat并不是一个线程安全的类 。在多线程情况下,会出现异常,想必有经验的小伙伴也遇到过。下面我们就来分析分析SimpleDateFormat为什么不安全?是怎么引发的?以及多线程下有那些SimpleDateFormat的解决方案?

先看看 《阿里巴巴开发手册》 对于SimpleDateFormat是怎么看待的:


二、问题场景复现


一般我们使用SimpleDateFormat的时候会把它定义为一个 静态变量 ,避免频繁创建它的对象实例,如下代码:

是不是感觉没什么毛病?单线程下自然没毛病了,都是运用到多线程下就有大问题了。 测试下:

控制台打印结果:

你看这不崩了?部分线程获取的时间不对,部分线程直接报 java.lang.NumberFormatException:multiple points 错,线程直接挂死了。


三、多线程不安全原因


因为我们把SimpleDateFormat定义为 静态变量 ,那么多线程下SimpleDateFormat的实例就会 被多个线程共享 ,B线程会读取到A线程的时间,就会出现时间差异和其它各种问题。SimpleDateFormat和它继承的DateFormat类也不是线程安全的。

来看看SimpleDateFormat的 format() 方法的源码:

注意, calendar.setTime(date),SimpleDateFormat的format方法实际操作的就是 Calendar

因为我们声明SimpleDateFormat为static变量,那么它的Calendar变量也就是一个共享变量, 可以被多个线程访问

假设线程A执行完calendar.setTime(date),把时间设置成2019-01-02,这时候被挂起,线程B获得CPU执行权。线程B也执行到了calendar.setTime(date),把时间设置为2019-01-03。线程挂起,线程A继续走,calendar还会被继续使用(subFormat方法),而这时calendar用的是线程B设置的值了,而这就是引发问题的根源,出现时间不对,线程挂死等等。

其实SimpleDateFormat源码上作者也给过我们提示:

意思就是:

日期格式不同步。

建议为每个线程创建单独的格式实例。

如果多个线程同时访问一种格式,则必须在外部同步该格式。


四、解决方案


只在需要的时候创建新实例,不用static修饰。

如上代码,仅在需要用到的地方创建一个新的实例,就没有线程安全问题,不过也加重了创建对象的负担, 会频繁地创建和销毁对象,效率较低

synchronized大法好

简单粗暴,synchronized往上一套也可以解决线程安全问题,缺点自然就是 并发量大的时候会对性能有影响,线程阻塞

ThreadLocal

ThreadLocal可以确保每个线程都可以得到单独的一个SimpleDateFormat的对象,那么自然也就不存在竞争问题了。

基于JDK1.8的DateTimeFormatter

也是《阿里巴巴开发手册》给我们的解决方案,对之前的代码进行改造:

运行结果就不贴了,不会出现报错和时间不准确的问题。

DateTimeFormatter源码上作者也加注释说明了, 他的类是不可变的,并且是线程安全的。







请到「今天看啥」查看全文