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