SimpleDateFormat线程安全问题
原创
©著作权归作者所有:来自51CTO博客作者Dongguabai的原创作品,请联系作者获取转载授权,否则将追究法律责任
SimpleDateFormat都知道是线程不安全的,在Java 8也出现了新的日期API,以后也不推荐使用SimpleDateFormat了。SimpleDateFormat线程不安全的原因也很简单,在format()方法中:


这个calendar变量被修改了,而这个calendar是一个共有的成员变量,在DateFormat中定义了一个protected属性的 Calendar类的对象:calendar。只是因为Calendar累的概念复杂,牵扯到时区与本地化等等,Jdk的实现中使用了成员变量来传递参数,这就造成在多线程的时候会出现错误。

而在后面的subFormat()方法中很多地方都使用到了calendar:

而一般我们使用SimpleDateFormat都是将其抽取成为一个公共的日期工具类,也就是说SimpleDateFormat对象本身是共有的,但是这个对象在调用方法的时候确对自身属性进行了修改,这在多线程环境下肯定会出现线程安全问题。
下面来演示一个示例:
package com.example.demoClient.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author Dongguabai
* @date 2018/11/2 17:50
*/
public class MyThread extends Thread{
private SimpleDateFormat sdf;
private String dateString;
public MyThread(SimpleDateFormat sdf, String dateString) {
this.sdf = sdf;
this.dateString = dateString;
}
@Override
public void run() {
try {
Date dateRef = sdf.parse(dateString);
String newDateString = sdf.format(dateRef);
if (!newDateString.equals(dateString)){
System.out.println("ThreadName="+this.getName()+"报错了 日期字符串:"+dateString+"转换成的日期为:"+newDateString);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
测试代码:
package com.example.demoClient.thread;
import java.text.SimpleDateFormat;
/**
* @author Dongguabai
* @date 2018/11/2 17:58
*/
public class Test {
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String[] dateStringArray = new String[]{"2000-01-01","2000-01-02","2000-01-03","2000-01-04","2000-01-05","2000-01-06","2000-01-07","2000-01-08","2000-01-09","2000-01-10"};
MyThread[] threadArray = new MyThread[10];
for (int i = 0; i < 10; i++) {
threadArray[i] = new MyThread(sdf,dateStringArray[i]);
}
for (int i = 0; i < 10; i++) {
threadArray[i].start();
}
}
}
运行结果:

使用单例的SimpleDateFormat出现了线程安全问题。
解决方案一
每次调用都new一个SimpleDateFormat。
改动也很简单:

运行结果:

控制台没有任何输出,运行正常。
解决方案二
使用ThreadLocal。每次获取SimpleDateFormat都从ThreadLocal中获取。也就是说在DateUtil中维护一个ThreadLocal<SimpleDateFormat>即可,每次直接从ThreadLocal中get即可。