阅读本文大概需要 3.2 分钟。

前言

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

下面我们就来分析分析SimpleDateFormat为什么不安全?是怎么引发的?以及多线程下有那些SimpleDateFormat的解决方案?

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

问题复现

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

打印一下结果:

是不是感觉没什么毛病?相信大多数人都是这样使用的,也包括我。在单线程下自然没毛病了,但是运用到多线程下就有大问题了。

测试下:

控制台打印结果:

你看结果,发现了什么?直接崩了,部分线程获取的时间不对,部分线程报java.lang.NumberFormatException:multiple points错,线程直接挂死了。还有部分线程报empty String错,值有问题。

多线程不安全原因

因为我们把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源码上作者也加注释说明了,他的类是不可变的,并且是线程安全的。

OK,现在是不是可以对你项目里的日期工具类进行一波优化了呢?

知识扩展

在上述代码中,我们通过创建一个线程池,来实现多线程循环打印日期的操作,但是我们创建方式你有没有留意。

ExecutorService executorService = Executors.newFixedThreadPool(100);

当你IDEA安装了阿里巴巴的代码规范检查插件时,使用Executors来创建线程池的话,会出现提示让你手动创建线程池。

因此,我们可以将创建线程池的代码改成:

ExecutorService executorService = new ThreadPoolExecutor(100, 100,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

但是又会有提示,建议要为线程池中的线程设置名称:

改造之后的代码为:

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
ExecutorService executorService = new ThreadPoolExecutor(100, 100,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

这里会有个问题,ThreadFactoryBuilder()在JDK1.8及之后被去除了,所以如果你的JDK低于1.8即可使用该方法,等于或高于1.8可采取其他方式设置线程名称,也可用其他方式手动创建线程池。

为什么要这样做

我们参考阿里巴巴的Java开发手册内容:

关于Executors

关于线程名称

再次简单进一步解读下:

  • newFixedThreadPool和newSingleThreadExecutor 由于最后一个参数即工作队列是

链表类型的阻塞队列,而我们看其构造函数发现,默认队列大小是整数的最大值!!!

所以如果请求太多,队列很可能就耗费内存非常大导致OOM。

但是他们的线程数是固定的,而且一般不会太大,所以不会因为创建过多线程而导致OOM。

  • 再来看下newCachedThreadPool和newScheduledThreadPool

其中第最大线程池大小是整数的最大值,因此线程可能不断创建,乃至到整数的最大值个线程,很容易导致OOM。其中工作队列使用的是 SynchronousQueue<E>,源码头部的注释中有说明(截取的部分)。

A {@linkplain BlockingQueue blocking queue} in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.

该类型的阻塞队列每一个插入操作必须等待对应的元素被另一个线程所移除,反之亦然。

因此阻塞队列不会无限拓展而导致OOM。

当我们学习和理解一些原则的同时,多注重源码分析!!!

·END·

程序员的成长之路

路虽远,行则必至

本文原发于 同名微信公众号「程序员的成长之路」,回复「1024」你懂得,给个赞呗。

微信ID:cxydczzl

往期精彩回顾

还在使用SimpleDateFormat?的更多相关文章

  1. 还在用SimpleDateFormat格式化时间?小心经理锤你

    还在用SimpleDateFormat格式化时间?小心经理锤你 场景 本来开开心心的周末时光,线上突然就疯狂报错,以为程序炸了,截停日志,发现是就是类似下述一段错误 java.lang.NumberF ...

  2. 还在用SimpleDateFormat?Java8都发布N年了,转LocalDateTime吧

    前言 Java8发布,已有数年之久,但是发现很多人都还是坚持着用SimpleDateFormat和Date进行时间操作.SimpleDateFormat这个类不是线程安全的,在使用的时候稍不注意,就会 ...

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

    如果是在多线程环境下,就需要注意了. 要点: 1.加Synchronized同步: 2.使用ThreadLocal: 3.jdk8使用DateTimeFormatter替代SimpleDateForm ...

  4. 推荐几个牛逼的 IDEA 插件,还带动图!

    阅读本文大概需要 2.3 分钟. 作者:纪莫, cnblogs.com/jimoer 这里只是推荐一下好用的插件,具体的使用方法不一一详细介绍. JRebel for IntelliJ 一款热部署插件 ...

  5. 为什么阿里Java规约要求谨慎使用SimpleDateFormat

    前言 在阿里Java开发规约中,有强制性的提到SimpleDateFormat 是线程不安全的类 ,在使用的时候应当注意线程安全问题,如下: 其实之前已经介绍过使用JDK1.8的DateTimeFor ...

  6. 为什么SimpleDateFormat不是线程安全的?

    一.前言 日期的转换与格式化在项目中应该是比较常用的了,最近同事小刚出去面试实在是没想到被 SimpleDateFormat 给摆了一道... 面试官:项目中的日期转换怎么用的?SimpleDateF ...

  7. 一个完整的 Web 请求到底发生了什么

    阅读本文大概需要 7 分钟. 一.从输入一个网址开始 当我们在浏览器输入一个网址,然后按下回车,接下来浏览器显示了页面.网速好的话这之间可能就一秒,但在这一秒内到底发生了什么? 本文主要内容是试图记录 ...

  8. 选择 25k 的 996 还是 18k 的 965

    阅读本文大概需要 3.7 分钟. 文中部分内容来源:http://h5ip.cn/hSDk 最近的 996.ICU 话题持续在火爆,一般热点新闻的热度持续时间为一星期,这次是程序们的集体发声导致,戳中 ...

  9. 阿里巴巴的26款超神Java开源项目!

    来源:https://segmentfault.com/a/1190000017346799 1.分布式应用服务开发的一站式解决方案 Spring Cloud Alibaba Spring Cloud ...

随机推荐

  1. MYSQL后更改ROOT的密码后无法启动服务报错1067

    安装MYSQL后更改了ROOT的密码后用 net start mysql 启动时报错1067.使用以下命令:1.进入mysql安装目录的bin目录下:cd C:\Program Files\MySQL ...

  2. VBA XML信息提取

    Option Explicit Sub 读取XML节点() '后期绑定 'Dim xdoc As Object 'Set xdoc = CreateObject("MSXML2.DOMDoc ...

  3. Collection<T> 的一个坑

    当前所在的公司偏好使用 Collection<T>(System.Collections.ObjectModel), 这货比起List<T>不仅少了很多实用方法, 而且还有一个 ...

  4. Module 的语法

    模块功能主要由两个命令构成:export和import.export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能. 一个模块就是一个独立的文件.该文件内部的所有变量,外部无法 ...

  5. 4、下行短信发送WebService、下行短信发送服务 -功能详细设计 --短信平台

    3.  下行短信发送WebService 开发一个WebService,供第三方系统调用,用于发送短信.WebService接收数据后,将信息存储入数据库的短信发送数据表中. WebService参数 ...

  6. 通过C#发送自定义的html格式邮件

    要发送HTML格式邮件,需要设置MailMessage对象的IsBodyHtml属性,设置为true. 类MailMessage在命名空间System.Net.Mail下.using System.N ...

  7. Ax2009中使用CLR发送邮件

    由于Ax2009系统方法SysMailer 发送中文的时候会乱码,一直找不到原因,用.NEt Framwork的类库可以解决中文乱码的问题.static void CKT_DotNetMail(Arg ...

  8. Charles抓https请求详细步骤

    1.电脑上安装好Charles 2.电脑上安装证书 (1)点击Help - SSL Proxying - Install Charlse Root Certificate (2)在电脑上找到证书.此时 ...

  9. idea实现热部署并且开启自动编译

    [注]本文转自https://blog.csdn.net/z15732621582/article/details/79439359博文,如有冒犯,请联系博主: 问题描述: 最近在调试代码并进行本地测 ...

  10. Java中的Integer和int

    Java中的Integer是引用类型,而int是基本类型.Integer是int的包装器类型. java中的基本类型有布尔类型boolean;字符类型char;整数类型byte,int,long,sh ...