还在使用SimpleDateFormat?

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

前言

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

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

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

1699de9099afa9f4?w=728&h=328&f=jpeg&s=38443

问题复现

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

1699de90998ce3ef?w=1080&h=679&f=jpeg&s=44435

打印一下结果:

1699de9099ca0f65?w=362&h=101&f=png&s=8075

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

测试下:

1699de909b1817a1?w=914&h=642&f=jpeg&s=45887

控制台打印结果:

1699de909b091eab?w=1080&h=737&f=jpeg&s=85446

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

多线程不安全原因

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

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

1699de909be957f0?w=592&h=757&f=jpeg&s=39179

注意, 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源码上作者也给过我们提示:

1699de90bc3dfd22?w=689&h=137&f=png&s=15666

翻译过来的意思就是:

日期格式未同步。

建议为每个线程创建单独的格式实例。
如果多个线程同时访问格式,则必须在外部同步

解决方案

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

1699de90bc512004?w=854&h=292&f=jpeg&s=29621

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

采用Synchronized方式

1699de90be47c023?w=1034&h=435&f=jpeg&s=31188

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

ThreadLocal

1699de90bf36f790?w=919&h=458&f=jpeg&s=36908

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

基于JDK1.8的DateTimeFormatter

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

1699de90c07dfbbd?w=792&h=727&f=jpeg&s=48359

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

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

1699de90c43882fa?w=455&h=90&f=png&s=8257

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

知识扩展

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

ExecutorService executorService = Executors.newFixedThreadPool(100);

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

1699de90e11a6939?w=824&h=872&f=jpeg&s=68971

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

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

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

1699de90e1620d23?w=952&h=393&f=jpeg&s=37986

改造之后的代码为:

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

1699de90e39f45d2?w=731&h=225&f=jpeg&s=33033

关于线程名称

1699de90e388fe09?w=574&h=181&f=png&s=30430

再次简单进一步解读下:

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

1699de90e40513c7?w=781&h=190&f=png&s=24319

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

1699de90e9fdd9d8?w=794&h=513&f=jpeg&s=37418

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

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

  • 再来看下newCachedThreadPool和newScheduledThreadPool

1699de910016b37b?w=769&h=217&f=jpeg&s=19970

其中第最大线程池大小是整数的最大值,因此线程可能不断创建,乃至到整数的最大值个线程,很容易导致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


往期精彩回顾

程序员接私活的7大平台利器

教你一招用 IDE 编程提升效率的骚操作!

大学期间的副业赚钱之道

一个对话让你明白架构师是做什么的?

作为程序员的你,一年看几本技术相关的书

5个相见恨晚的Linux命令

为啥程序员下班后只关显示器从不关电脑?

送给程序员们的经典电子书大礼包

面试时如何优雅地自我介绍?

支撑百万并发的数据库架构如何设计?


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69902700/viewspace-2638927/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/69902700/viewspace-2638927/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值