「日常開發」記一次因使用Date引起的線上BUG處理

Coder小明發表於2020-04-21

生活中,我們需要掌控自己的時間,減少加班,提高效率;日常開發中,我們需要操作時間API,保證效率、安全、穩定。現在都2020年了,瞭解如何在JDK8及以後的版本中更好地操控時間就很有必要,尤其是一次線上BUG的發生,讓小明更是深有體會。

背景

Java8以前,每每操控時間,我們經常使用的類庫就是Date,並且會通過SimpleDateFormat類對時間進行格式化。你可知道?Date類是一個可變類,SimpleDateFormat類也是執行緒不安全的,因此在多執行緒的場景下執行格式化操作時,就會發生意想不到的情況。下面我們看一下使用DateSimpleDateFormat在多執行緒下可能發生的問題以及使用LocalDateTimeDateTimeFormatter的方法和優勢。

問題來了

多執行緒環境下,使用DateSimpleDateFormat時,如果我們將它定義為一個靜態變數使用,雖然會避免重複建立例項, 但是會出現個別執行緒獲取時間失敗的現象,我們通過程式碼模擬這個場景:

執行main方法,檢視控制檯會發現有個別執行緒會報java.lang.NumberFormatException異常。類似下圖所示:

問題分析

接下來,我們通過檢視原始碼進一步分析(多圖預警),可以看到SimpleDateFormat是直接繼承的DateFormat類:

並重寫了parse()(字串轉日期)和 format()(日期轉字串)方法,因此我們重點從這兩個方法來分析。

首先是SimpleDateFormatparse()方法,該方法中建立了一個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類。

基於Java8DateTimeFormatter的解決方案,我們對之前的程式碼進行改造,多執行緒環境下,執行程式碼,並未發現任何異常,穩定高效:

我們可以看到在DateTimeFormatter原始碼上作者也貼心的加註釋說明,該類是不可變的,並且是執行緒安全的。

同理,這點我們也可以從LocalDateTime的官方原始碼中看出。

其他騷操作

為了讓大家忘掉之前使用Calendar操作時間的笨拙,我們來切實感受一下LocalDateTime給實際開發中帶來的便利:

更多舉例說明,請點選文末閱讀原文

程式碼地址:https://github.com/WhenCoding/coder-xiaoming

總結

綜上,小明推薦小夥伴們使用JDK8的LocalDateTime系列來取代Date系列,這樣做不僅能夠保證線上專案平穩執行,而且通過其自帶的API還能操作時間,還能提高開發效率,今晚可以不加班!

歡迎大家訪問我的個人部落格網站:https://mynamecoder.com

相關文章