SimpleDateFormatt线程不安全及解决办法
一. 为什么SimpleDateFormat不是线程安全的?
Java源码如下:
-
/**
-
* Date formats are not synchronized.
-
* It is recommended to create separate format instances for each thread.
-
* If multiple threads access a format concurrently, it must be synchronized
-
* externally.
-
*/
-
public class SimpleDateFormat extends DateFormat {
-
-
public Date parse(String text, ParsePosition pos){
-
calendar.clear(); // Clears all the time fields
-
// other logic ...
-
Date parsedDate = calendar.getTime();
-
}
-
}
-
-
-
abstract class DateFormat{
-
// other logic ...
-
protected Calendar calendar;
-
public Date parse(String source) throws ParseException{
-
ParsePosition pos = new ParsePosition( 0);
-
Date result = parse(source, pos);
-
if (pos.index == 0)
-
throw new ParseException( "Unparseable date: \"" + source + "\"" ,
-
pos.errorIndex);
-
return result;
-
}
-
}
如果我们把SimpleDateFormat定义成static成员变量,那么多个thread之间会共享这个sdf对象, 所以Calendar对象也会共享。
假定线程A和线程B都进入了parse(text, pos) 方法, 线程B执行到calendar.clear()后,线程A执行到calendar.getTime(), 那么就会有问题。
二. 解决方案
1.方法内部创建SimpleDateFormat,不管是什么时候,将有线程安全的对象由共享变为私有局部变量都可以避免多线程问题,不过也加重了创建对象的负担,虽然随时创建SimpleDateFormat会造成一定的性能影响,而且会对GC产生一定的压力,但这并不是核心问题,只要能产生正确的结果:
-
public static String format(Date date) {
-
if (date == null) {
-
return "";
-
}
-
return new SimpleDateFormat(YMD_HYPHEN_PATTERN).format(date);
-
}
2.将SimpleDateFormat进行同步使用,在每次执行时都对其加锁,这样也会影响性能,想要调用此方法的线程就需要block,当多线程并发量比较大时会对性能产生一定影响;
-
public static String formatDate(Date date)throws ParseException{
-
synchronized(sdf){
-
return sdf.format(date);
-
}
-
}
在任何公共的地方使用该类时,都需要对SimpleDateFormat进行加锁。
3.使用ThreadLocal变量,用空间换时间,这样每个线程就会独立享有一个本地的SimpleDateFormat变量;
-
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
-
-
protected DateFormat initialValue() {
-
return new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss");
-
}
-
};
-
-
public static Date parse(String dateStr) throws ParseException {
-
return threadLocal.get().parse(dateStr);
-
}
写一个工具类:
-
public class DateUtil {
-
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>();
-
-
public static Date parse(String str) throws Exception {
-
SimpleDateFormat sdf = local.get();
-
if (sdf == null) {
-
sdf = new SimpleDateFormat( "dd-MMM-yyyy", Locale.US);
-
local.set(sdf);
-
}
-
return sdf.parse(str);
-
}
-
-
public static String format(Date date) throws Exception {
-
SimpleDateFormat sdf = local.get();
-
if (sdf == null) {
-
sdf = new SimpleDateFormat( "dd-MMM-yyyy", Locale.US);
-
local.set(sdf);
-
}
-
return sdf.format(date);
-
}
-
}
这样就可以保证每个线程的本地变量都是安全的,不同线程之间并不共享相同的SimpleDateFormat,从而避免了线程安全问题。
如果需要对性能比较敏感,可以采用这种方式,至少比前两种的速度要快,但是占用内存也会大一点(但也不会多么夸张)。