生活中,我們需要掌控自己的時間,減少加班,提高效率;日常開發中,我們需要操作時間API,保證效率、安全、穩定。現在都2020年了,瞭解如何在JDK8及以後的版本中更好地操控時間就很有必要,尤其是一次線上BUG的發生,讓小明更是深有體會。
背景
在Java8以前,每每操控時間,我們經常使用的類庫就是Date,並且會通過SimpleDateFormat類對時間進行格式化。你可知道?Date類是一個可變類,SimpleDateFormat類也是執行緒不安全的,因此在多執行緒的場景下執行格式化操作時,就會發生意想不到的情況。下面我們看一下使用Date、SimpleDateFormat在多執行緒下可能發生的問題以及使用LocalDateTime、DateTimeFormatter的方法和優勢。
問題來了
多執行緒環境下,使用Date、SimpleDateFormat時,如果我們將它定義為一個靜態變數使用,雖然會避免重複建立例項, 但是會出現個別執行緒獲取時間失敗的現象,我們通過程式碼模擬這個場景:
執行main方法,檢視控制檯會發現有個別執行緒會報java.lang.NumberFormatException異常。類似下圖所示:
問題分析
接下來,我們通過檢視原始碼進一步分析(多圖預警),可以看到SimpleDateFormat是直接繼承的DateFormat類:
並重寫了parse()(字串轉日期)和 format()(日期轉字串)方法,因此我們重點從這兩個方法來分析。
首先是SimpleDateFormat的parse()方法,該方法中建立了一個CalendarBuilder物件,
再往下看,會看到CalendarBuilder使用establish方法將變數calendar設值到其屬性中,
![image-20200420012213545](/Users/xin/Library/Application Support/typora-user-images/image-20200420012213545.png)
而calendar是父類DateFormat類的共享變數,可以被多個執行緒訪問到
因此當SimpleDateFormat宣告為static時,執行緒並不安全,多個執行緒同時操作訪問就會丟擲異常。
同樣地通過檢視format(),我們發現format方法中有一行calendar.setTime(date)
;也是操作的該共享變數calendar,執行緒也是不安全的。
有趣的是,在DateFormat原始碼註釋上作者也已經給出醒目的提示:
使用Google翻譯過來就是
日期格式不同步。 建議為每個執行緒建立單獨的格式例項。 如果多個執行緒同時訪問一種格式,則必須在外部同步該格式。
解決方案
小明有一句座右銘,方法總比問題多。我們來看幾個小明認為不錯的解決方案。
1、僅在需要用到的地方建立一個新的例項,就沒有執行緒安全問題。
點評:加重了建立物件的負擔,頻繁地建立和銷燬物件,消耗資源,效率較低。
2、通過synchronized解決執行緒安全問題;
點評:併發量大的時候會對效能有影響,容易造成執行緒阻塞。
3、通過ThreadLocal保證執行緒之間變數不共享
點評:ThreadLocal可以確保每個執行緒都可以得到單獨的一個SimpleDateFormat的物件,那麼自然也就不存在競爭問題了。就是有點大材小用。
以上就是小明能夠提供的所有方案。什麼,都不滿意?我們來看一下2020年JDK8的解決方案。
使用LocalDateTime
在Java8以後,我們有了新的選擇,使用LocalDateTime時間類。首先,LocalDateTime本身是執行緒安全的,其對應的格式化工具類DateTimeFormatter也是執行緒安全的,不存在變數共享,每一個屬性欄位都用了final關鍵字修飾,因此每次操作後都是返回的copy物件。並且LocalDateTime類本身也有很多操作時間的API來替代傳統的Calendar類。
基於Java8的DateTimeFormatter的解決方案,我們對之前的程式碼進行改造,多執行緒環境下,執行程式碼,並未發現任何異常,穩定高效:
我們可以看到在DateTimeFormatter原始碼上作者也貼心的加註釋說明,該類是不可變的,並且是執行緒安全的。
同理,這點我們也可以從LocalDateTime的官方原始碼中看出。
其他騷操作
為了讓大家忘掉之前使用Calendar操作時間的笨拙,我們來切實感受一下LocalDateTime給實際開發中帶來的便利:
更多舉例說明,請點選文末閱讀原文
總結
綜上,小明推薦小夥伴們使用JDK8的LocalDateTime系列來取代Date系列,這樣做不僅能夠保證線上專案平穩執行,而且通過其自帶的API還能操作時間,還能提高開發效率,今晚可以不加班!
歡迎大家訪問我的個人部落格網站:https://mynamecoder.com