一. 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());