SimpleDateFormat线程安全问题

在需要进行日期与字符串相互转换的类中,经常会声明一个静态的SimpleDateFormat变量,如下所示:

1
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

然后就可以直接使用sdf的format和parse方法进行日期与字符串的相互转换,但是SimpleDateFormat并不是线程安全的,我们使用如下代码验证SimpleDateFormat的线程安全问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Test {

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static String[] dates = new String[]{"2015-07-01 10:45:52", "2015-07-02 09:34:11",
"2015-07-01 07:12:32", "2015-07-03 15:06:54", "2015-07-02 21:12:38",
"2015-07-01 01:59:01", "2015-07-02 13:51:18", "2015-07-03 10:05:08"};

public static class MyThread extends Thread {
public void run() {
for (int i=0; i<100; i++) {
try {
System.out.println(Thread.currentThread().getName() + "\t" + sdf.format(sdf.parse(dates[new Random().nextInt(8)])));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) throws ParseException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}

MyThread线程循环100次,每次从dates数组中随机取出一个日期字符串,依次进行parse和format操作后再输出最终的日期字符串,若声明一个MyThread线程运行,则输出如图所示:
1
输出的日期字符串都在dates数组中,说明parse和format操作均正常。若声明两个MyThread线程运行,则输出如图所示:
1
输出的日期字符串有不在dates数组中的,且还存在异常,说明parse和format操作并不是线程安全的。
在多线程下若要使用SimpleDateFormat,一种方法是每次进行日期与字符串转换时,声明一个SimpleDateFormat变量,但这样会产生过多的变量实例,另一种方法是使用ThreadLocal为每个线程保存一个SimpleDateFormat变量实例,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DateUtil {

private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
protected synchronized SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};

public static SimpleDateFormat get() {
return threadLocal.get();
}

public static Date parse(String s) throws ParseException {
return get().parse(s);
}

public static String format(Date d) {
return get().format(d);
}

}

同时修改MyThread,使用DateUtil进行日期与字符串转换,如下所示:

1
2
3
4
5
6
7
8
9
10
11
public static class MyThread extends Thread {
public void run() {
for (int i=0; i<100; i++) {
try {
System.out.println(Thread.currentThread().getName() + "\t" + DateUtil.format(DateUtil.parse(dates[new Random().nextInt(8)])));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

再声明两个MyThread线程运行,则输出正常。