今天我和大家一起聊一聊SimpleDateFormat ,這都2020年了,怎麼還在用SimpleDateFormat ?
其實,作爲一名Java 程序員,我們會經常在編程時候和時間打交道,比如要把某一個時間存儲到數據庫中去,而我們直接使用
Date date = new Date();
System.out.println(date);
得到的時間都是這樣的
Sun Jun 07 17:22:52 CST 2020
因此,我們經常需要把時間進行格式化處理,然後在進行存儲,方便閱讀。這個時候我們就會使用到SimpleDateFormat 類,比如使用下面的代碼來獲取當前時間,並調用SimpleDateFormat 對時間進行格式化:
Date date = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
String datestr = df.format(date);
System.out.println(datestr);
最終輸出的時間爲
2020-06-07 16:45:58
由於在java 8之前 SimpleDateFormat 是一個比較常用的類,但是我還是在這裏要建議開發者不要用 SimpleDateForma。原因有兩點:
首先,通過new 一個對象來操作對象,佔用內存大,如果每處理一個時間信息的時候,就需要new一個SimpleDateFormat實例對象,然後再丟棄這個對象。大量的對象就這樣被創建出來,佔用大量的內存和 jvm空間。
那麼很多人就會想,既然new 代價太大, 不如我們使用 static 將其設置爲共享變量,這樣就可以減小頻繁創建對象帶來的內存開銷啦,真的是機智如我。
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
但是這樣操作在併發量非常大的情況下,由於 SimpleDateFormat 是線程不安全的,這也是第二點原因,這個在JDK文檔中已經明確表明了SimpleDateFormat不應該用在多線程場景中:
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.
在《阿里巴巴 Java 開發手冊》中也明確表示不要定義SimpleDateFormat 爲static 變量,如果定義static 必須要加鎖。
這背後的原因是由於 SimpleDateFormat 中的format方法在執行過程中,會使用一個成員變量calendar來保存時間。
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
this.calendar.setTime(date);
boolean useDateFormatSymbols = this.useDateFormatSymbols();
int i = 0;
……
由於在聲明SimpleDateFormat的時候,使用的是static定義的。那麼這個SimpleDateFormat就是一個共享變量,SimpleDateFormat 中的calendar也就可以被多個線程訪問到。
舉個例子:假設一個線程A剛執行完calendar.setTime把時間設置成2020-05-07,這個線程還沒執行完,線程B又執行了calendar.setTime把時間改成了2020-06-07。這時候線程A繼續往下執行,拿到的calendar.getTime得到的時間就是線程B改過之後的。
除了format方法以外,SimpleDateFormat的parse方法也有同樣的問題。
所以,不要把SimpleDateFormat作爲一個共享變量使用。
那麼如何解決這樣的問題呢?如果你使用的是Java 8 之前的JDK,那麼上面的《阿里巴巴Java 開發手冊》已經給出瞭解決方案,那就是使SimpleDateFormat 變成線程安全的,通過加鎖的方式來解決
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
而如果你使用的是Java 8 + 的版本,那麼你完全可以拋棄這種線程不安全的時間格式化方法。可以使用DateTimeFormatter代替SimpleDateFormat,這是一個線程安全的格式化工具類。
LocalDate 和LocalDateTime
Java 8開始,明確了日期時間概念,例如:瞬時(instant)、 長短(duration)、日期、時間、時區和週期。
同時繼承了Joda 庫按人類語言和計算機各自解析的時間處理方式。不同於老版本,新API基於ISO標準日曆系統,java.time包下的所有類都是不可變類型而且線程安全。
關鍵類
-
Instant:瞬時實例。
-
LocalDate:本地日期,不包含具體時間 例如:2014-01-14 可以用來記錄生日、紀念日、加盟日等。
-
LocalTime:本地時間,不包含日期。
-
LocalDateTime:組合了日期和時間,但不包含時差和時區信息。
-
ZonedDateTime:最完整的日期時間,包含時區和相對UTC或格林威治的時差。
新API還引入了 ZoneOffSet 和 ZoneId 類,使得解決時區問題更爲簡便。解析、格式化時間的 DateTimeFormatter 類也全部重新設計。
例如,我們使用LocalDate 代替Date,使用DateTimeFormatter 代替SimpleDateFormat,如下所示:
String DateNow = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")); // 當前日期和時間
System.out.println(DateNow);
這樣就避免了SimpleDateFormat 的線程不安全問題啦。