我將從以下幾點進行說明:
1、SimpleDateFormat的使用
2、為什么SimpleDateFormat線程不安全呢?
3、怎樣解決SimpleDateFormat的線程不安全對象
4、總結
SimpleDateFormat的使用
我們通常都會寫一個日期處理工具類DateUtils,使用時直接使用這個實例來進行操作。代碼如下:
那么怎么使用呢?
DateUtils.parseString("2020-05-01 10:02:02")
上述代碼的調用,在大部分的時間里都會工作的很好,但是當你的項目并發比較高的時候,問題就出來了,比如轉化的時間不正確,比如報錯,線程掛死。我們看下下面案例:
執行輸入如下:
報java.lang.NumberFormatException: multiple points錯誤,直接掛死,沒起來;
還有下面問題:我們只是解析 2020-05-01 10:02:02,下面輸出結果卻是各種各樣的結果。
為什么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
Date formats 是線程不安全的。推薦為每個線程創建單獨的format實例。如果多線程并發訪問同一個format實例,必須加同步操作。
那下面我們分析源碼來說明為什么線程不安全?
因為我們在工具類中把SimpleDateFormat定義為靜態變量,那么在多線程環境下SimpleDateFormat就會被多線程共享,B線程會讀取到A線程的時間,就會出現時間差異和其他問題。
那我們來看parse做了什么?
從上面代碼看(3)(4)(5) 操作不是原子性,當多個線程調用parse方法適合,比如A執行了(3)(4),也就是設置了cal對象,在執行代碼(5) 前線程B 執行了代碼(3) 清空了cal對象,由于多個線程使用的是一個cal對象,所以線程A執行(5) 的時候返回的是被線程B清空后的對象
怎樣解決SimpleDateFormat的線程不安全對象
(1) 每次使用時new一個SimpleDateFormat的 實例,這樣可以保證每個實例使用自己的Calendar實例,但是每次使用都需要new一個對象,并且使用后由于沒有其他引用,又需要回收,開銷會很大。
(2) 可以使用synchronized對SimpleDtaFormat實例進行同步
(3) 使用ThreadLocal,這樣每個線程只需要使用一個SimpleDateFormate實例,這相比第一種方式 節省了對象的創建銷毀開銷,并且不需要使多個線程同步。
(4) 使用JDK8中的DateTimeFormatter
上面說明此類是線程安全的。
總結
SimpleDateFormat是線程不安全的類,一般不要定義為 static 變量,如果定義為 static ,
必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
說明:如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。