1. 程式人生 > >為什麼阿里巴巴禁止把SimpleDateFormat定義為static型別的?

為什麼阿里巴巴禁止把SimpleDateFormat定義為static型別的?

在日常開發中,我們經常會用到時間相關類,我們有很多辦法在Java程式碼中獲取時間。但是不同的方法獲取到的時間的格式都不盡相同,這時候就需要一種格式化工具,把時間顯示成我們需要的格式。

最常用的方法就是使用SimpleDateFormat類。這是一個看上去功能比較簡單的類,但是,一旦使用不當也有可能導致很大的問題。

在阿里巴巴Java開發手冊中,有如下明確規定:

那麼,本文就圍繞SimpleDateFormat的用法、原理等來深入分析下如何以正確的姿勢使用它。

SimpleDateFormat用法

SimpleDateFormat是Java提供的一個格式化和解析日期的工具類。它允許進行格式化(日期 -> 文字)、解析(文字 -> 日期)和規範化。SimpleDateFormat 使得可以選擇任何使用者定義的日期-時間格式的模式。

在Java中,可以使用SimpleDateFormat的format方法,將一個Date型別轉化成String型別,並且可以指定輸出格式。

// Date轉String
Date data = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dataStr = sdf.format(data);
System.out.println(dataStr);

以上程式碼,轉換的結果是:2018-11-25 13:00:00,日期和時間格式由”日期和時間模式”字串指定。如果你想要轉換成其他格式,只要指定不同的時間模式就行了。

在Java中,可以使用SimpleDateFormat的parse方法,將一個String型別轉化成Date型別。

// String轉Data
System.out.println(sdf.parse(dataStr));

日期和時間模式表達方法

在使用SimpleDateFormat的時候,需要通過字母來描述時間元素,並組裝成想要的日期和時間模式。常用的時間元素和字母的對應表如下:

模式字母通常是重複的,其數量確定其精確表示。如下表是常用的輸出格式的表示方法。

輸出不同時區的時間

時區是地球上的區域使用同一個時間定義。以前,人們通過觀察太陽的位置(時角)決定時間,這就使得不同經度的地方的時間有所不同(地方時)。1863年,首次使用時區的概念。時區通過設立一個區域的標準時間部分地解決了這個問題。

世界各個國家位於地球不同位置上,因此不同國家,特別是東西跨度大的國家日出、日落時間必定有所偏差。這些偏差就是所謂的時差。

現今全球共分為24個時區。由於實用上常常1個國家,或1個省份同時跨著2個或更多時區,為了照顧到行政上的方便,常將1個國家或1個省份劃在一起。所以時區並不嚴格按南北直線來劃分,而是按自然條件來劃分。例如,中國幅員寬廣,差不多跨5個時區,但為了使用方便簡單,實際上在只用東八時區的標準時即北京時間為準。

由於不同的時區的時間是不一樣的,甚至同一個國家的不同城市時間都可能不一樣,所以,在Java中想要獲取時間的時候,要重點關注一下時區問題。

預設情況下,如果不指明,在建立日期的時候,會使用當前計算機所在的時區作為預設時區,這也是為什麼我們通過只要使用new Date()就可以獲取中國的當前時間的原因。

那麼,如何在Java程式碼中獲取不同時區的時間呢?SimpleDateFormat可以實現這個功能。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(sdf.format(Calendar.getInstance().getTime()));

以上程式碼,轉換的結果是: 2018-11-24 21:00:00 。既中國的時間是11月25日的13點,而美國洛杉磯時間比中國北京時間慢了16個小時(這還和冬夏令時有關係,就不詳細展開了)。

如果你感興趣,你還可以嘗試列印一下美國紐約時間(America/New_York)。紐約時間是2018-11-25 00:00:00。紐約時間比中國北京時間慢了13個小時。

當然,這不是顯示其他時區的唯一方法,不過本文主要為了介紹SimpleDateFormat,其他方法暫不介紹了。

SimpleDateFormat執行緒安全性

由於SimpleDateFormat比較常用,而且在一般情況下,一個應用中的時間顯示模式都是一樣的,所以很多人願意使用如下方式定義SimpleDateFormat:

public class Main {
   private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
   public static void main(String[] args) {
       simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
       System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime()));
   }
}

這種定義方式,存在很大的安全隱患。

問題重現

我們來看一段程式碼,以下程式碼使用執行緒池來執行時間輸出。

/** * @author Hollis */ 
public class Main {

   /**
    * 定義一個全域性的SimpleDateFormat
    */

   private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

   /**
    * 使用ThreadFactoryBuilder定義一個執行緒池
    */

   private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
       .setNameFormat("demo-pool-%d").build();

   private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
       0L, TimeUnit.MILLISECONDS,
       new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

   /**
    * 定義一個CountDownLatch,保證所有子執行緒執行完之後主執行緒再執行
    */

   private static CountDownLatch countDownLatch = new CountDownLatch(100);

   public static void main(String[] args) {
       //定義一個執行緒安全的HashSet
       Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
       for (int i = 0; i < 100; i++) {
           //獲取當前時間
           Calendar calendar = Calendar.getInstance();
           int finalI = i;
           pool.execute(() -> {
                   //時間增加
                   calendar.add(Calendar.DATE, finalI);
                   //通過simpleDateFormat把時間轉換成字串
                   String dateString = simpleDateFormat.format(calendar.getTime());
                   //把字串放入Set中
                   dates.add(dateString);
                   //countDown
                   countDownLatch.countDown();
           });
       }
       //阻塞,直到countDown數量為0
       countDownLatch.await();
       //輸出去重後的時間個數
       System.out.println(dates.size());
   }
}

以上程式碼,其實比較容易理解。就是迴圈一百次,每次迴圈的時候都在當前時間基礎上增加一個天數(這個天數隨著迴圈次數而變化),然後把所有日期放入一個執行緒安全的帶有去重功能的Set中,然後輸出Set中元素個數。

上面的例子我特意寫的稍微複雜了一些,不過我幾乎都加了註釋。這裡面涉及到了執行緒池的建立、CountDownLatch、lambda表示式、執行緒安全的HashSet等知識。感興趣的朋友可以逐一瞭解一下。

正常情況下,以上程式碼輸出結果應該是100。但是實際執行結果是一個小於100的數字。

原因就是因為SimpleDateFormat作為一個非執行緒安全的類,被當做了共享變數在多個執行緒中進行使用,這就出現了執行緒安全問題。

在阿里巴巴Java開發手冊的第一章第六節——併發處理中關於這一點也有明確說明:

那麼,接下來我們就來看下到底是為什麼,以及該如何解決。

執行緒不安全原因

通過以上程式碼,我們發現了在併發場景中使用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 externally.

那麼接下來分析下為什麼會出現這種問題,SimpleDateFormat底層到底是怎麼實現的?

我們跟一下SimpleDateFormat類中format方法的實現其實就能發現端倪。

SimpleDateFormat中的format方法在執行過程中,會使用一個成員變數calendar來儲存時間。這其實就是問題的關鍵。

由於我們在宣告SimpleDateFormat的時候,使用的是static定義的。那麼這個SimpleDateFormat就是一個共享變數,隨之,SimpleDateFormat中的calendar也就可以被多個執行緒訪問到。

假設執行緒1剛剛執行完calendar.setTime把時間設定成2018-11-11,還沒等執行完,執行緒2又執行了calendar.setTime把時間改成了2018-12-12。這時候執行緒1繼續往下執行,拿到的calendar.getTime得到的時間就是執行緒2改過之後的。

相關推薦

為什麼阿里巴巴禁止SimpleDateFormat定義static型別的?

在日常開發中,我們經常會用到時間相關類,我們有很多辦法在Java程式碼中獲取時間。但是不同的方法獲取到的時間的格式都不盡相同,這時候就需要一種格式化工具,把時間顯示成我們需要的格式。 最常用的方法就是使用SimpleDateFormat類。這是一個看上去功能比較簡單的類,但是,一旦使用不當也有可能導致很

什麽阿裏巴巴禁止SimpleDateFormat定義static類型的?

tps executors get acc 避免 extern ini timezone sta 在日常開發中,我們經常會用到時間,我們有很多辦法在Java代碼中獲取時間。但是不同的方法獲取到的時間的格式都不盡相同,這時候就需要一種格式化工具,把時間顯示成我們需要的格式。最

為什麼阿里巴巴禁止工程師直接使用日誌系統(Log4j、Logback)中的 API

作為Java程式設計師,我想很多人都知道日誌對於一個程式的重要性,尤其是Web應用。很多時候,日誌可能是我們瞭解應用程式如何執行的唯一方式。 所以,日誌在Java Web應用中至關重要,但是,很多人卻以為日誌輸出只是一件簡單的事情,所以會經常忽略和日誌相關的問題。 在接下來的幾篇文章中,我會來介紹介紹

c# 呼叫阿里巴巴釘釘自定義機器人介面發訊息。

using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Syste

為什麼阿里巴巴禁止開發人員使用isSuccess作為變數名

在日常開發中,我們會經常要在類中定義布林型別的變數,比如在給外部系統提供一個RPC介面的時候,我們一般會定義一個欄位表示本次請求是否成功的。 關於這個”本次請求是否成功”的欄位的定義,其實是有很多種講究和坑的,稍有不慎就會掉入坑裡,作者在很久之前就遇到過類似的問題,本文就來圍繞這個簡單分

【轉】 為什麼阿里巴巴禁止直接使用日誌系統中的 API?

作為Java程式設計師,我想很多人都知道日誌對於一個程式的重要性,尤其是Web應用。很多時候,日誌可能是我們瞭解應用程式如何執行的唯一方式。 所以,日誌在Java Web應用中至關重要,但是,很多人卻以為日誌輸出只是一件簡單的事情,所以會經常忽略和日誌相關的問題。 在接下來的幾篇文章中,我會來介紹介紹這個

為什麼阿里巴巴禁止開發人員使用isSuccess作為變數名?

在日常開發中,我們會經常要在類中定義布林型別的變數,比如在給外部系統提供一個RPC介面的時候,我們一般會定義一個欄位表示本次請求是否成功的。 關於這個”本次請求是否成功”的欄位的定義,其實是有很多種講究和坑的,稍有不慎就會掉入坑裡,作者在很久之前就遇到過類似的問題,本文就來圍繞這個簡單分析一下。

結構體可不可以宣告定義Static?

不可以. 結構體是一種使用者自定義型別,跟標準型別是一樣的,只是由使用者自己定義的罷了。型別是不能宣告為Static的,只有變數才能宣告為Static。因為型別只是一種抽象,不分配記憶體,具體的變數才能分配記憶體。而且Static變數是在編譯的時候分配記憶體的。可以將結構體

golang struct結構體方法中的引數需要定義指標型別

前幾日寫一個網頁的簡單計數器問題時發現,計數器居然永遠為0,計數器不計數,見鬼了。。。 程式碼如下: type Counter struct { n int } func (ctr Counter) ServeHTTP(c http.ResponseWriter, r

Qt:QString轉換 double型別

把QString轉換為 double型別 方法1.QString str=”123.45”; double val=str.toDouble(); //val=123.45 方法2.很適合科學計數法形式轉換 bool ok; double d; d=

java中為什麼要main方法定義一個static方法?

我們知道,在C/C++當中,這個main方法並不是屬於某一個類的,它是一個全域性的方法,所以當我們執行的時候,c++編譯器很容易的就能找到這個main方法。 然而當我們執行一個java程式的時候,因為java都是以類作為程式的組織單元,當我們要執行的時候,我們

eclipse 運行錯誤:在類XXX中找不到 main 方法, 請將 main 方法定義: public static void main(String[] args) 否則 JavaFX 應用程序類必須擴展javafx.application.Application

分享圖片 java stat 報錯 es2017 pub .... img nbsp 新建了一個類Hello: 代碼: 第一次運行報錯: 點擊關閉該類的界面時出現: 點擊是,然後再次打開,可以正確執行,結果為: 這是為什麽.... ec

C中什麽情況下局部變量定義局部靜態變量

spf image ref get 調用 一次 不用 變量定義 .com 首先要說明,數組不是變量;C中稱它是具有相同類型元素的集合,嚴格說來它是一種簡單的數據結構——這是題外話。定義在函數中的自動型(就是不用static修飾)數組,函數被調用時才創建,而函數結束後就自動

錯誤: 在類 Main 中找不到 main 方法, 請將 main 方法定義: public static void main(String[] args) 否則 JavaFX 應用程序類必須擴展javafx.application.Application

導包 javafx 其他 就是 ring del args bsp pub 錯誤: 在類 Main 中找不到 main 方法, 請將 main 方法定義為: public static void main(String[] args)否則 JavaFX 應用程序類必須擴展

一輛學校班車裡面能裝多少個高爾夫球 Google 谷歌 百度 baidu 阿里巴巴 alibaba 微軟 華 hu

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

阿里巴巴資料中心雙11守夜人:機器當“媳婦”,願做億萬網友背後的男人

11月11日零點,全世界最熱鬧的時刻,你一定想不到這裡——張北。 張北不只有音樂節,還有關乎雙11成敗的——阿里巴巴資料中心。 你買的每一件商品,交易資料流都將在這裡匯聚,每一盞亮起的伺服器巡視燈,就意味著數以億計的資料流。 可以說,有了這顆“技術心臟”,阿里巴巴才能更好承載這項跨越全球的超級工程。

MUI框架-14-使用自定義icon圖示、引入阿里巴巴向量圖示

MUI框架-14-使用自定義icon圖示、引入阿里巴巴向量圖示 首先介紹介紹一下,前端必備的非常強大的 阿里巴巴向量圖示庫:地址是:http://www.iconfont.cn/ 這裡有豐富,精美,且免費使用的向量圖示 怎麼應用到自己的專案中呢? 方法一:直接下載,png 格式的圖示 提示:可以自

錯誤: 在類 com.js.sort.ArraySort 中找不到 main 方法, 請將 main 方法定義: public static void main(String[] args) 否則 JavaFX 應用程式類必須擴充套件javafx.application.Application

https://blog.csdn.net/liu1340308350/article/details/80746671 開啟: eclipse ->window->preference->run and debug->Lunching    將第一行Sav

2018阿里巴巴線上程式設計題--將陣列分割和相等的三段

看到一道面試題: 給定一個int型的陣列,找出兩個位置,使得陣列被分為三段,每段之和相等,問存不存在這樣的兩個位置,注意兩個位置上的數字不屬於任何一段。要求時間複雜度為O(n)。 用雙迴圈的話,可以很容易的做到,但是時間複雜度是O(n2),不滿足要求。可以利用字首和、字尾和的概念來解決。 用J

錯誤: 在類中找不到 main 方法, 請將 main 方法定義: public static void main(String[] args) 否則

錯誤: 在類 ZiFUChuan.Pyramid 中找不到 main 方法, 請將 main 方法定義為:    public static void main(String[] args) 否則 JavaFX 應用程式類必須擴充套件javafx.application.Ap