一、背景

写 java 的应该都不陌生,SimpleDateFormart 是一个处理时间格式的一个类,它提供的 api 基本上能满足我们对时间格式解析的需求,比如说把日期格式转成 yyyy-MM-dd HH:mm:ss 等等,也是我们平时 coding 中比较常用的,但可能很多人都不太清楚,或者说没有注意到其实 SimpleDateFormart 并不是一个线程安全的,尤其是在一些非高并发的系统中,可能不会出现这样的问题从而未被发现,但其实 jdk 文档已经给出了描述,表面此类并不是线程安全的。

我们也从它的源码上看下,为什么不是线程安全的,源码太多我们贴下核心的地方,SimpleDateFormat 实例里面有一个 Calendar 对象,之所以是线程不安全的是因为其中存放日期数据的变量都是线程不安全的,establish 中 calendar .clear();calendar.setWeekDate() 方法都不是原子性操作。

Ok,接下来我们写个 demo 去验证下这个问题,这里我起了多个线程去处理时间,会出现下面的一个结果,这里已经出现了线程安全的问题

二、如何解决

1.粗暴一点,每次使用的时候 new 一个去使用,这样当然不存在线程安全问题,但是这样重复的 new 对象也浪费了内存,我们不这样做,我们是一个优秀的 “攻城狮”
2.加锁,在使用 SimpleDateFormat 的地方加把锁,但是这样会降低处理的效率,我们会这样做吗?作为一个优秀的 “攻城狮”
3.使用 ThreadLocal 来解决,这也是我今天重点讲的一种方式。
那么 ThreadLocal 为什么能解决这个问题呢?经常做并发编程的同学们对于 ThreadLocal 相信并不陌生,每个线程都维护着一个 ThreadLocalMap 的字段用来存放 ThreadLocal 对象。相当于每个线程都有一份自己的数据,达到了线程隔离的目的,那么当然就不会出现线程安全的问题。

三、实现 + 效果

直接上代码,我们定义一个 ThreadLocal 变量,在需要使用的地方使用 get 方法,get 方法就会从 ThreadLocalMap 中获取 ThreadLocal 对象,源码可以看到这里它用当前线程去获取 ThreadLocalMap,实际上就是前面我们说的,每个线程都维护着一个 ThreadLocalMap 的字段

Ok,我们现在再看下执行结果,可以看到已经正常执行,解决了线程安全的问题

另外再补充下,ThreadLocalMap 的 key 为 ThreadLocal 实例,它为一个弱引用,弱引用有利于 GC 回收。当 ThreadLocal 的 key == null 时,GC 就会回收这部分空间,但是 value 不会被回收,因为它与当前线程存在强引用关系,如果这个线程对象不销毁那么这个强引用关系则会一直存在,就会出现内存泄漏情况。所以说只要这个线程对象能够及时被 GC 回收,就不会出现内存泄漏。如果碰到线程池,那就坑了。在源码中,ThreadLocal 在 set 时通过 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值以及 Entry 对象本身。当然我们也可以显示调用 ThreadLocal 的 remove() 方法进行处理

五、总结

这里我主要介绍了通过 ThreadLocal 来解决 SimpleDateFormat 线程安全的问题,当然,也还有一些线程安全的工具类来处理时间格式大家可以使用,比如 LocalDateTime,DateTimeFormatter,如果大家有其他方式欢迎留言。


↙↙↙阅读原文可查看相关链接,并与作者交流