威脅描述
Java常用SimpleDateFormat
做時間轉(zhuǎn)換滤蝠,但SimpleDateFormat
不是線程安全的,如果SimpleDateFormat
用static聲明或只實例化一次被多個線程使用,并發(fā)度高時可導(dǎo)致用戶看到其他用戶數(shù)據(jù)疏叨。
威脅說明
SimpleDateFormat
中的 parse()
和 format()
方法不是線程安全的腌逢,當(dāng)在多線程的并發(fā)環(huán)境里浅萧,把它定義為全局靜態(tài)變量逐沙,將導(dǎo)致用戶看到其他用戶數(shù)據(jù)的競態(tài)條件漏洞哲思。(詳細參見:SimpleDateFormat為什么不是線程安全的)
受影響的還有:
SimpleDateFormat
的父類持有的對象Calendar
洼畅,在多線程中會出現(xiàn)A線程設(shè)置Calendar
值后被B線程修改,或者A和B線程同時設(shè)置Calendar
的值棚赔,都會出現(xiàn)錯誤帝簇。SimpleDateFormat
持有對象DecimalFormat
也不是線程安全的
修復(fù)建議
方案1:使用同步鎖(降低性能)
最簡單的方法就是在做日期轉(zhuǎn)換之前,為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(增加內(nèi)存開銷)
另外一個方法就是使用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
兩個類都沒有線程問題,只要不把它們創(chuàng)建為共享變量就沒有線程問題竖伯。
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
等對象相互轉(zhuǎn)化氛赐,極大的方便了程序的兼容性。
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();
}
}