新聞中心
閱讀本文大概需要 3.2 分鐘。
創(chuàng)新互聯(lián)服務項目包括博望網站建設、博望網站制作、博望網頁制作以及博望網絡營銷策劃等。多年來,我們專注于互聯(lián)網行業(yè),利用自身積累的技術優(yōu)勢、行業(yè)經驗、深度合作伙伴關系等,向廣大中小型企業(yè)、政府機構等提供互聯(lián)網行業(yè)的解決方案,博望網站推廣取得了明顯的社會效益與經濟效益。目前,我們服務的客戶以成都為中心已經輻射到博望省份的部分城市,未來相信會繼續(xù)擴大服務區(qū)域并繼續(xù)獲得客戶的支持與信任!
前言
日常開發(fā)中,我們經常需要使用時間相關類,想必大家對 SimpleDateFormat 并不陌生。主要是用它進行時間的 格式化輸出和解析 ,挺方便快捷的,但是 SimpleDateFormat并不是一個線程安全的類 。在多線程情況下,會出現(xiàn)異常,想必有經驗的小伙伴也遇到過。
下面我們就來分析分析SimpleDateFormat為什么不安全?是怎么引發(fā)的?以及多線程下有那些SimpleDateFormat的解決方案?
先看看 《阿里巴巴開發(fā)手冊》 對于SimpleDateFormat是怎么看待的
cdn.xitu.io/2019/3/21/1699de9099afa9f4?w=728&h=328&f=jpeg&s=38443">
問題復現(xiàn)
一般我們在使用SimpleDateFormat的時候會把它定義為一個靜態(tài)變量,避免頻繁創(chuàng)建它們的對象實例,代碼如下:
打印一下結果:
是不是感覺沒什么毛?。肯嘈糯蠖鄶?shù)人都是這樣使用的,也包括我。在單線程下自然沒毛病了,但是運用到多線程下就有大問題了。
測試下:
控制臺打印結果:
你看結果,發(fā)現(xiàn)了什么?直接崩了,部分線程獲取的時間不對,部分線程報 java.lang.NumberFormatException:multiple points 錯,線程直接掛死了。還有部分線程報 empty String 錯,值有問題。
多線程不安全原因
因為我們把SimpleDateFormat定義為 靜態(tài)變量 ,那么多線程下SimpleDateFormat的實例就會 被多個線程共享 ,B線程會讀取到A線程的時間,就會出現(xiàn)時間差異和其它各種問題。SimpleDateFormat和它繼承的DateFormat類也不是線程安全的。
來看看 SimpleDateFormat 的 format() 方法的源碼:
注意, calendar.setTime(date),SimpleDateFormat的format方法實際操作的就是 Calendar 。
因為我們聲明SimpleDateFormat為static變量,那么它的Calendar變量也就是一個共享變量, 可以被多個線程訪問 。
假設線程A執(zhí)行完calendar.setTime(date),把時間設置成2019-01-02,這時候被掛起,線程B獲得CPU執(zhí)行權。線程B也執(zhí)行到了calendar.setTime(date),把時間設置為2019-01-03。線程掛起,線程A繼續(xù)走,calendar還會被繼續(xù)使用(subFormat方法),而這時calendar用的是線程B設置的值了,而這就是引發(fā)問題的根源,出現(xiàn)時間不對,線程掛死等等。
其實SimpleDateFormat源碼上作者也給過我們提示:
翻譯過來的意思就是:
日期格式未同步。
建議為每個線程創(chuàng)建單獨的格式實例。
如果多個線程同時訪問格式,則必須在外部同步
解決方案
只在需要的時候創(chuàng)建新實例,不用static修飾 。
如上代碼,僅在需要用到的地方創(chuàng)建一個新的實例,就沒有線程安全問題,不過也加重了創(chuàng)建對象的負擔, 會頻繁地創(chuàng)建和銷毀對象,效率較低 。
采用Synchronized方式
簡單粗暴,synchronized往上一套也可以解決線程安全問題,缺點自然就是 并發(fā)量大的時候會對性能有影響,線程阻塞 。
ThreadLocal
ThreadLocal可以確保每個線程都可以得到單獨的一個SimpleDateFormat的對象,那么自然也就不存在競爭問題了。
基于JDK1.8的DateTimeFormatter
也是《阿里巴巴開發(fā)手冊》給我們的解決方案,對之前的代碼進行改造:
運行結果就不貼了,不會出現(xiàn)報錯和時間不準確的問題。
DateTimeFormatter源碼上作者也加注釋說明了, 他的類是不可變的,并且是線程安全的。
OK,現(xiàn)在是不是可以對你項目里的日期工具類進行一波優(yōu)化了呢?
知識擴展
在上述代碼中,我們通過創(chuàng)建一個線程池,來實現(xiàn)多線程循環(huán)打印日期的操作,但是我們創(chuàng)建方式你有沒有留意。
ExecutorService executorService = Executors.newFixedThreadPool(100);
當你IDEA安裝了阿里巴巴的代碼規(guī)范檢查插件時,使用Executors來創(chuàng)建線程池的話,會出現(xiàn)提示讓你手動創(chuàng)建線程池。
因此,我們可以將創(chuàng)建線程池的代碼改成:
ExecutorService executorService = new ThreadPoolExecutor(100, 100,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
但是又會有提示,建議要為線程池中的線程設置名稱:
改造之后的代碼為:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build(); ExecutorService executorService = new ThreadPoolExecutor(100, 100,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
這里會有個問題, ThreadFactoryBuilder() 在JDK1.8及之后被去除了,所以如果你的JDK低于1.8即可使用該方法,等于或高于1.8可采取其他方式設置線程名稱,也可用其他方式手動創(chuàng)建線程池。
為什么要這樣做
我們參考阿里巴巴的Java開發(fā)手冊內容:
關于Executors
關于線程名稱
再次簡單進一步解讀下:
newFixedThreadPool和newSingleThreadExecutor 由于最后一個參數(shù)即工作隊列是
鏈表類型的阻塞隊列,而我們看其構造函數(shù)發(fā)現(xiàn),默認隊列大小是整數(shù)的最大值?。?!
所以如果請求太多,隊列很可能就耗費內存非常大導致OOM。
但是他們的線程數(shù)是固定的,而且一般不會太大,所以不會因為創(chuàng)建過多線程而導致OOM。
再來看下newCachedThreadPool和newScheduledThreadPool
其中第最大線程池大小是整數(shù)的最大值,因此線程可能不斷創(chuàng)建,乃至到整數(shù)的最大值個線程,很容易導致OOM。其中工作隊列使用的是 SynchronousQueue
A {@linkplain BlockingQueue blocking queue} in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.
該類型的阻塞隊列每一個插入操作必須等待對應的元素被另一個線程所移除,反之亦然。
因此阻塞隊列不會無限拓展而導致OOM。
當我們學習和理解一些原則的同時,多注重源碼分析?。?!
·END·
程序員的成長之路
路雖遠,行則必至
本文原發(fā)于 同名微信公眾號「程序員的成長之路」,回復「1024」你懂得,給個贊唄。
微信ID:cxydczzl
往期精彩回顧
程序員接私活的7大平臺利器
教你一招用 IDE 編程提升效率的騷操作!
大學期間的副業(yè)賺錢之道
一個對話讓你明白架構師是做什么的?
作為程序員的你,一年看幾本技術相關的書
5個相見恨晚的Linux命令
為啥程序員下班后只關顯示器從不關電腦?
送給程序員們的經典電子書大禮包
面試時如何優(yōu)雅地自我介紹?
支撐百萬并發(fā)的數(shù)據(jù)庫架構如何設計?
新聞名稱:還在使用SimpleDateFormat?
當前鏈接:http://www.dlmjj.cn/article/gieohs.html