SimpleDateFormat都知道是线程不安全的,在Java 8也出现了新的日期API,以后也不推荐使用SimpleDateFormat了。SimpleDateFormat线程不安全的原因也很简单,在format()方法中:

SimpleDateFormat线程安全问题_线程安全

SimpleDateFormat线程安全问题_线程安全_02

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

SimpleDateFormat线程安全问题_java_03

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

SimpleDateFormat线程安全问题_java_04

而一般我们使用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线程安全问题_线程安全_05

使用单例的SimpleDateFormat出现了线程安全问题。

解决方案一

每次调用都new一个SimpleDateFormat。

改动也很简单:

SimpleDateFormat线程安全问题_java_06

运行结果:

SimpleDateFormat线程安全问题_java_07

控制台没有任何输出,运行正常。

解决方案二

使用ThreadLocal。每次获取SimpleDateFormat都从ThreadLocal中获取。也就是说在DateUtil中维护一个ThreadLocal<SimpleDateFormat>即可,每次直接从ThreadLocal中get即可。