日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么阿里巴巴禁止把SimpleDateFormat定义为static类型的?

發布時間:2025/3/21 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么阿里巴巴禁止把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());

當然,以上代碼也有改進空間,就是,其實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為每一個線程單獨創建一個和使用Java8中的DateTimeFormatter類代替等。

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

總結

以上是生活随笔為你收集整理的为什么阿里巴巴禁止把SimpleDateFormat定义为static类型的?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。