威胁描述
Java常用SimpleDateFormat
做时间转换,但SimpleDateFormat
不是线程安全的,如果SimpleDateFormat
用static声明或只实例化一次被多个线程使用,并发度高时可导致用户看到其他用户数据。
威胁说明
SimpleDateFormat
中的 parse()
和 format()
方法不是线程安全的,当在多线程的并发环境里,把它定义为全局静态变量,将导致用户看到其他用户数据的竞态条件漏洞。(详细参见:SimpleDateFormat为什么不是线程安全的)
受影响的还有:
SimpleDateFormat
的父类持有的对象Calendar
,在多线程中会出现A线程设置Calendar
值后被B线程修改,或者A和B线程同时设置Calendar
的值,都会出现错误。SimpleDateFormat
持有对象DecimalFormat
也不是线程安全的
修复建议
方案1:使用同步锁(降低性能)
最简单的方法就是在做日期转换之前,为DateFormat对象加锁。这种方法使得一次只能让一个线程访问DateFormat对象,而其他线程只能等待。
public class Common {
private static SimpleDateFormat dateFormat;
...
public synchronized String format1(Date date) {
return dateFormat.format(date);
}
public String format2(Date date) {
synchronized(dateFormat)
{
return dateFormat.format(date);
}
}
}
方案2:使用ThreadLocal(增加内存开销)
另外一个方法就是使用ThreadLocal
变量去容纳DateFormat
对象,也就是说每个线程都有一个属于自己的副本,并无需等待其他线程去释放它。这种方法会比使用同步块更高效。
public class DateFormatTest {
private final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public Date convert(String source) throws ParseException {
return df.get().parse(source);
}
}
方案3:JDK1.8及以上,使用DateTimeFormatter类(推荐)
JDK1.8中新增了 LocalDate
与 LocalDateTime
等类来解决日期处理方法,同时引入了一个新的类 DateTimeFormatter
来解决日期格式化问题。
LocalDateTime
、DateTimeFormatter
两个类都没有线程问题,只要不把它们创建为共享变量就没有线程问题。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
LocalDate date = LocalDate.parse("2017 06 17", formatter);
System.out.println(formatter.format(date));
方案4:使用 FastDateFormat 类
FastDateFormat
是apache
的commons-lang3
包提供的,FastDateFormat
是线程安全的,可以直接使用,不必考虑多线程的情况
public class DateUtil {
public static final FastDateFormat FORMAT_yyyyMMddHHmmss=FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
/**
* 最常用的格式化10位时间戳为yyyyMMddHHmmss
*/
public static String getNormalTime(String timestamp){
return FORMAT_yyyyMMddHHmmss.format(getDate(timestamp));
}
}
方案5:使用Joda-Time(推荐)
Joda-Time
是一个很棒的开源的 JDK 的日期和日历 API 的替代品,其 DateTimeFormat
是线程安全而且不变的。
Joda-Time
对比 FastDateFormat
,不仅仅可以对时间进行格式化输出,而且可以生成瞬时时间值,并与Calendar
、Date
等对象相互转化,极大的方便了程序的兼容性。
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.util.Date;
public class DateFormatTest {
private final DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyyMMdd");
public Date convert(String source){
DateTime d = fmt.parseDateTime(source);
returnd.toDate();
}
}