為什么阿里巴巴禁止把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) {//定義一個線程安全的HashSetSet<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);//countDowncountDownLatch.countDown();});}//阻塞,直到countDown數量為0countDownLatch.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改過之后的。

除了format方法以外,SimpleDateFormat的parse方法也有同樣的問題。

所以,不要把SimpleDateFormat作為一個共享變量使用。

如何解決

前面介紹過了SimpleDateFormat存在的問題以及問題存在的原因,那么有什么辦法解決這種問題呢?

解決方法有很多,這里介紹三個比較常用的方法。

使用局部變量

for (int i = 0; i < 100; i++) {//獲取當前時間Calendar calendar = Calendar.getInstance();int finalI = i;pool.execute(() -> {// SimpleDateFormat聲明成局部變量SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//時間增加calendar.add(Calendar.DATE, finalI);//通過simpleDateFormat把時間轉換成字符串String dateString = simpleDateFormat.format(calendar.getTime());//把字符串放入Set中dates.add(dateString);//countDowncountDownLatch.countDown();});
}
復制代碼

SimpleDateFormat變成了局部變量,就不會被多個線程同時訪問到了,就避免了線程安全問題。

加同步鎖

除了改成局部變量以外,還有一種方法大家可能比較熟悉的,就是對于共享變量進行加鎖。

for (int i = 0; i < 100; i++) {//獲取當前時間Calendar calendar = Calendar.getInstance();int finalI = i;pool.execute(() -> {//加鎖synchronized (simpleDateFormat) {//時間增加calendar.add(Calendar.DATE, finalI);//通過simpleDateFormat把時間轉換成字符串String dateString = simpleDateFormat.format(calendar.getTime());//把字符串放入Set中dates.add(dateString);//countDowncountDownLatch.countDown();}});
}
復制代碼

通過加鎖,使多個線程排隊順序執行。避免了并發導致的線程安全問題。

其實以上代碼還有可以改進的地方,就是可以把鎖的粒度再設置的小一點,可以只對simpleDateFormat.format這一行加鎖,這樣效率更高一些。

使用ThreadLocal

第三種方式,就是使用 ThreadLocal。 ThreadLocal 可以確保每個線程都可以得到單獨的一個 SimpleDateFormat 的對象,那么自然也就不存在競爭問題了。

/*** 使用ThreadLocal定義一個全局的SimpleDateFormat*/
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}
};//用法
String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());
復制代碼

用 ThreadLocal 來實現其實是有點類似于緩存的思路,每個線程都有一個獨享的對象,避免了頻繁創建對象,也避免了多線程的競爭。

當然,以上代碼也有改進空間,就是,其實SimpleDateFormat的創建過程可以改為延遲加載。這里就不詳細介紹了。

使用DateTimeFormatter

如果是Java8應用,可以使用DateTimeFormatter代替SimpleDateFormat,這是一個線程安全的格式化工具類。就像官方文檔中說的,這個類 simple beautiful strong immutable thread-safe。

//解析日期
String dateStr= "2016年10月25日";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate date= LocalDate.parse(dateStr, formatter);//日期轉換為字符串
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm a");
String nowStr = now .format(format);
System.out.println(nowStr);
復制代碼

總結

本文介紹了SimpleDateFormat的用法,SimpleDateFormat主要可以在String和Date之間做轉換,還可以將時間轉換成不同時區輸出。同時提到在并發場景中SimpleDateFormat是不能保證線程安全的,需要開發者自己來保證其安全性。

主要的幾個手段有改為局部變量、使用synchronized加鎖、使用Threadlocal為每一個線程單獨創建一個等。

希望通過此文,你可以在使用SimpleDateFormat的時候更加得心應手。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/281269.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/281269.shtml
英文地址,請注明出處:http://en.pswp.cn/news/281269.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于信息收集和加工的思考

隨著互聯網的發展&#xff0c;獲取信息的手段越來越多&#xff0c;我們對手機的依賴程度超乎想象&#xff0c;每天忙碌著&#xff0c;大腦接收著豐富的信息&#xff0c;感覺每天都學習到了很多的知識。但我們對學習經常會有些誤區&#xff1a;1、書買了擺在書架上&#xff0c;看…

[譯]關于NODE_ENV,哪些你應該了解

原文 Node.js開發者經常檢測環境變量NODE_ENV&#xff0c;但你是否知道設置這個值同時也具有著某些別的意義&#xff1f;閱讀本文你將發現這些。NODE_ENV是一個在Express框架中極其常用的環境變量。用其確定應用的運行環境&#xff08;諸如開發&#xff0c;staging&#xff0c;…

GatewayWorker Not Support On Windows.

thinkphp版本&#xff1a;5.1 tp5.1運行命令行php think worker:gateway出現GatewayWorker Not Support On Windows.是因為在tp5.1的命令行中做了判定&#xff0c;不支持windows環境下運行。 這里不支持windows環境并不是說gateway worker不支持windows&#xff0c;而是tp5.1的…

8支團隊正在努力構建下一代Ethereum

“我們不想在構建 Ethereum 2.0時重新造輪子。” 談到開發人員為 Ethereum 區塊鏈進行兩個獨立的升級&#xff08;一個稱為 Ethereum 2.0&#xff0c;另一個稱為 Ethereum 1x&#xff09;所作出的補充努力&#xff0c;勞爾喬丹堅持認為&#xff0c;在較短的時間內將升級包括在 …

fastjson SerializerFeature詳解

名稱含義備注QuoteFieldNames輸出key時是否使用雙引號,默認為true UseSingleQuotes使用單引號而不是雙引號,默認為false WriteMapNullValue是否輸出值為null的字段,默認為false WriteEnumUsingToStringEnum輸出name()或者original,默認為false UseISO8601DateFormatDate使用ISO…

費曼學習法中問題的提出與反問,擴展與主動查詢的學習習慣訓練過程

在2022年11月05日的對話中&#xff0c;九遷先講了女媧補天和女媧造人的故事&#xff0c;女媧造人的故事還講了兩個版本的&#xff0c;隨后提到了一個事情&#xff0c;那就是&#xff0c;如果你要找一個神仙一起度過一天&#xff0c;你想找誰&#xff0c;想做些什么&#xff1f;…

Fiddle:使用斷點:bpu,bpafter

http://www.cnblogs.com/yoyoketang/p/6778006.html轉載于:https://www.cnblogs.com/peixianping/p/7230021.html

windows環境下TP5.1使用think-worker(Workerman/GatewayWorker)

文章目錄首先是解決如何運行gatewayworker調試gatewayworker程序向指定客戶端發送消息在TP框架中調用Gateway的API總結說明測試環境 windows10&#xff1b;PHP7.2&#xff1b;TP5.1&#xff1b; 這里只介紹如何使用TP集成的workerman擴展庫think-worker&#xff0c;原生workerm…

webpack之DefinePlugin使用

DefinePlugin是webpack注入全局變量的插件&#xff0c;通常使用該插件來判別代碼運行的環境變量。在使用該插件需要注意的是&#xff0c;如果在該插件配置了相關的參數&#xff0c;必須要源碼中使用&#xff0c;webpack才會注入。例如&#xff1a; new webpack.DefinePlugin({p…

Magicodes.IE 2.7.0發布

2.7.02022.11.07使用SkiaSharp替代SixLabors.ImageSharp移除SixLabors.Fonts感謝linch90的大力支持&#xff08;具體見pr#462&#xff09;部分方法改為虛方法2.7.0-beta2022.10.27使用SixLabors.ImageSharp替代System.Drawing&#xff0c;感謝linch90 &#xff08;見pr#454&…

Mobx 與 Redux 的性能對比

在本文中你將看到我最終得出的結論是 Mobx 的性能優于 Redux。但很明顯這樣的結論是片面的&#xff0c;甚至是有失偏頗的&#xff0c;因為我只選取了一個的場景對兩者進行測試。可能真實的情況恰恰相反&#xff0c;Mobx 僅僅在我測試的這個場景中優于 Redux&#xff0c;但是在我…

linux lsof/netstat查看進程和端口號相關命令:

本文為博主原創&#xff0c;未經允許不得轉載&#xff1a; 在linux操作時&#xff0c;經常要查看運行的項目的進程和端口號&#xff0c;在這里總結了以下常用到的相關命令&#xff1a; 1.查看系統運行的java項目&#xff0c;并查看進程號 這個用到的命令為&#xff1a; ps -ef|…

C#高級編程9 第17章 使用VS2013-C#特性

C#高級編程9 第17章 使用VS2013 編輯定位到 如果默認勾選了這項&#xff0c;請去掉勾選&#xff0c;因為勾選之后解決方案的目錄會根據當前文件選中。 可以設置項目并行生成數 版本控制軟件設置 所有文本編輯器行號顯示 啟用編輯繼續 收集調試信息&#xff0c;將影響性能 Code …

還在手畫C#依賴關系圖嗎?快來試試這個工具吧!

還在手畫C#依賴關系圖嗎&#xff1f;快來試試這個工具吧&#xff01;筆者最近見到了一個不錯的工具&#xff0c;可以讓大家在看代碼的時候一鍵生成C#依賴的類圖。非常適合編寫文檔、查看和學習開源項目設計時使用&#xff0c;比如下方就是筆者通過這個工具生成的Microsoft.Exte…

Web服務器 - Apache配置介紹

基本語法 常量的定義與使用&#xff0c;使用關鍵詞 Define 可以定義常量&#xff0c;使用 ${} 插入常量&#xff0c;如下 語法規則說明示列Define定義常量Define SRVROOT “D:/srv/Apache24”${}使用常量ServerRoot “${SRVROOT}”/表示路徑時使用 / 而不使用 \D:/srv/Apache…

點火開關分為4個檔位,分別是off,acc,IG-on,和ST

off全車除了常火&#xff08;如應急燈&#xff0c;時鐘等的記憶功能&#xff09;外&#xff0c;均不供電。acc 是附件檔&#xff0c;部分車載附屬設備供電&#xff0c;如視聽系統&#xff0c;儀表燈&#xff0c;燈光等。也就是說&#xff0c;車停在哪里&#xff0c;發動機不轉&…

h5的formData 上傳文件及.net后臺

先來前端的代碼&#xff1a; html 代碼&#xff1a; <input type"file" id"files" value"" multiple/> js代碼&#xff1a; function init() {var ele_files document.querySelector("#files");ele_files.addEventListener(&qu…

51 Nod 1027 大數乘法【Java大數亂搞】

1027 大數乘法 基準時間限制&#xff1a;1 秒 空間限制&#xff1a;131072 KB 分值: 0 難度&#xff1a;基礎題 給出2個大整數A,B&#xff0c;計算A*B的結果。Input第1行&#xff1a;大數A 第2行&#xff1a;大數B (A,B的長度 < 1000&#xff0c;A,B > 0&#xff09; Out…

關于ASP.NET Core WebSocket實現集群的思考

前言提到WebSocket相信大家都聽說過&#xff0c;它的初衷是為了解決客戶端瀏覽器與服務端進行雙向通信&#xff0c;是在單個TCP連接上進行全雙工通訊的協議。在沒有WebSocket之前只能通過瀏覽器到服務端的請求應答模式比如輪詢&#xff0c;來實現服務端的變更響應到客戶端&…

windows環境下Apache+PHP+MySQL搭建服務器

相關文件下載 下載地址Apachehttps://www.apachehaus.com/cgi-bin/download.plxPHPhttps://windows.php.net/downloadMySQLhttps://dev.mysql.com/downloads/mysql/MySQL MySQL配置 當前使用的MySQL版本是8.0.18&#xff0c;在MySQL根目錄下新建my.ini文件&#xff0c;下面是…