SimpleDateFormat线程不安全问题

一、背景

      日常查看真线日志过程中发现,日期转换异常,日志如下

查看代码如下:

二 、原因分析:

      上述代码使中SimpleDateFormat是全局共享变量,但是SimpleDateFormat是线程不安全的,在多线程场景下,会导致全局共享变量被篡改; 在正常的测试中,都没有问题,但一旦在生产环境中一定负载情况下时,这个问题就出来了,它会出现各种不同的情况,比如转化的时间不正确,比如报错,比如线程被挂死等等;源码中注释如下:

SimpleDateFormat继承了DateFormat,在DateFormat中定义了一个protected属性的 Calendar类的对象:calendar,Jdk的实现中使用了成员变量来传递参数,会造成在多线程的时候会出现错误。
例:有两个线程持有了同一个SimpleDateFormat的实例,分别调用format方法:
  线程1调用format方法,改变了calendar这个字段。
  中断来了。
  线程2开始执行,它也改变了calendar。
  又中断了。
  线程1回来了,此时,calendar已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢calendar对象,则会出现各种奇怪的问题,时间不对,线程挂死,参数异常等等。
  分析一下format的实现,我们不难发现,用到成员变量calendar,唯一的好处,就是在调用subFormat时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
这个问题背后隐藏着一个更为重要的问题--无状态:无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format方法在运行过程中改动了SimpleDateFormat的calendar字段,所以,它是有状态的。这也同时提醒我们在开发和设计系统的时候注意下一下三点:
  1.自己写公用类的时候,要对多线程调用情况下的后果在注释里进行明确说明
  2.对线程环境下,对每一个共享的可变变量都要注意其线程安全性
  3.我们的类和方法在做设计的时候,要尽量设计成无状态的
详细解析可以参考:[1] https://www.jianshu.com/p/eec34fbd07c7;[2] https://www.cnblogs.com/ITtangtang/p/7583762.html;

三、解决方案

1、将SimpleDateFormat定义成局部变量;
缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。
2、加一把线程同步锁:synchronized(lock);
缺点:性能较差,每次都要等待锁释放后其他线程才能进入;
3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本;
4、使用DateTimeFormatter代替SimpleDateFormat;
5、使用第三方的工具类 例如org.apache.commons.lang3.time.FastDateFormat,
关于FastDateFormat和SimpleDateFormat 性能对比 可以参考[3] http://itindex.net/detail/54674-simpledateformat-fastdateformat-%E6%B5%8B%E8%AF%95

参考阿里巴巴规范:
阿里巴巴规范

你可能感兴趣的:(SimpleDateFormat线程不安全问题)