你還在使用Java中的SimpleDateFormat嗎?

今天我和大家一起聊一聊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 的線程不安全問題啦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章