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

生活中,我們需要掌控自己的時間,減少加班,提高效率;日常開發中,我們需要操作時間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

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