1. 程式人生 > 其它 >SimpleDateFormat執行緒不安全的解決方案【多執行緒下注意】

SimpleDateFormat執行緒不安全的解決方案【多執行緒下注意】

一. SimpleDateFormat
想必大家對 SimpleDateFormat 並不陌生。SimpleDateFormat 是 Java 中一個非常常用的類,他是以區域敏感的方式格式化和解析日期的具體類。 它允許格式化 (date -> text)、語法分析 (text -> date)和標準化。

1. 日期時間格式(例如:yyyy-MM-dd HH:mm:ss)

2. 例項說明(Date–>Text)

SimpleDateFormat myFmt = new SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒");
SimpleDateFormat myFmt1 
= new SimpleDateFormat("yy/MM/dd HH:mm"); SimpleDateFormat myFmt2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//等價於now.toLocaleString() SimpleDateFormat myFmt3 = new SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒 E "); SimpleDateFormat myFmt4 = new SimpleDateFormat("一年中的第 D 天 一年中第w個星期 一月中第W個星期 在一天中k時 z時區"); Date now
= new Date(); System.out.println(myFmt.format(now)); System.out.println(myFmt1.format(now)); System.out.println(myFmt2.format(now)); System.out.println(myFmt3.format(now)); System.out.println(myFmt4.format(now)); System.out.println(now.toGMTString()); System.out.println(now.toLocaleString()); System.out.println(now.toString());

3. 例項說明(Text–>Date)

String time1 = "2018年06月19日 23時10分05秒";
String time2 = "18/06/19 23:10";
String time3 = "2018-06-19 23:10:05";
String time4 = "2018年06月19日 23時10分05秒 星期二"; SimpleDateFormat myFmt = new SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒");
SimpleDateFormat myFmt1 = new SimpleDateFormat("yy/MM/dd HH:mm");
SimpleDateFormat myFmt2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//等價於now.toLocaleString()
SimpleDateFormat myFmt3 = new SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒 E");
Date date1 = null;
try {
date1 = myFmt.parse(time1);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date1);

Date date2 = null;
try {
date2 = myFmt1.parse(time2);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date2);

Date date3 = null;
try {
date3 = myFmt2.parse(time3);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date3);

Date date4 = null;
try {
date4 = myFmt3.parse(time4);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date4);

結果:

4.執行緒不安全
多個執行緒同時對一個SimpleDateFormat物件進行操作的時候,就會出現錯亂。
例如全域性的private static final SimpleDateFormat df = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
檢視SimpleDateFormat 原始碼:calendar為protected型別的全域性變數,當執行緒A設定了calendar.setTime(date);
執行緒B又設定了一次,那麼calendar值就變了,執行緒A在subFormat方法中用到的calendar就不是自己設定的而是執行緒B設定的。

這個問題背後隱藏著一個更為重要的問題–無狀態:無狀態方法的好處之一,就是它在各種環境下,都可以安全的呼叫。衡量一個方法是否是有狀態的,就看它是否改動了其它的東西,比如全域性變數,比如例項的欄位。format 方法在執行過程中改動了 SimpleDateFormat 的 calendar 欄位,所以,它是有狀態的。

5. 解決辦法
1.每個執行緒使用的時候建立新的SimpleDateFormat,將有執行緒安全問題的物件由共享變為區域性私有都能避免多執行緒問題
2.使用同步:同步 SimpleDateFormat 物件

public class DateSyncUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date) throws ParseException {
synchronized (sdf) {
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException {
synchronized (sdf) {
return sdf.parse(strDate);
}
}
}

說明:當執行緒較多時,當一個執行緒呼叫該方法時,其他想要呼叫此方法的執行緒就要 block 等待,多執行緒併發量大的時候會對效能有一定的影響

3.使用 ThreadLocal

public class DateSyncUtil {
private ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public Date parse(String dateStr){
return threadLocal.get().parse(dateStr);
}
public String format(Date date) {
return threadLocal.get().format(date);
}
}

說明:使用 ThreadLocal, 也是將共享變數變為獨享,執行緒獨享肯定能比方法獨享在併發環境中能減少不少建立物件的開銷。如果對效能要求比較高的情況下,一般推薦使用這種方法。

二. DateTimeFormatter
Java 8 提供了新的日期時間 API,其中包括用於日期時間格式化的 DateTimeFormatter,它與 SimpleDateFormat 最大的區別在於:DateTimeFormatter 是執行緒安全的,而 SimpleDateFormat 並不是執行緒安全。

//Text-->Date

String dateStr= "2018-06-20 11:25:56";
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse(dateStr, dateTimeFormatter);
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDateTime.atZone(zone).toInstant();
Date date = Date.from(instant);

//Date-->Text
Date date = new Date();
Instant instant = date.toInstant();
ZoneId zone = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String nowStr = localDateTime.format(format);
System.out.println(nowStr);

//LocalDateTime獲取毫秒
System.out.println(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());