摘要:線程掛起,線程繼續(xù)走,還會被繼續(xù)使用方法,而這時用的是線程設(shè)置的值了,而這就是引發(fā)問題的根源,出現(xiàn)時間不對,線程掛死等等。建議為每個線程創(chuàng)建多帶帶的格式實(shí)例。其中工作隊(duì)列使用的是,源碼頭部的注釋中有說明截取的部分。
閱讀本文大概需要 3.2 分鐘。
前言
日常開發(fā)中,我們經(jīng)常需要使用時間相關(guān)類,想必大家對SimpleDateFormat并不陌生。主要是用它進(jìn)行時間的格式化輸出和解析,挺方便快捷的,但是SimpleDateFormat并不是一個線程安全的類。在多線程情況下,會出現(xiàn)異常,想必有經(jīng)驗(yàn)的小伙伴也遇到過。
下面我們就來分析分析SimpleDateFormat為什么不安全?是怎么引發(fā)的?以及多線程下有那些SimpleDateFormat的解決方案?
先看看《阿里巴巴開發(fā)手冊》對于SimpleDateFormat是怎么看待的
問題復(fù)現(xiàn)
一般我們在使用SimpleDateFormat的時候會把它定義為一個靜態(tài)變量,避免頻繁創(chuàng)建它們的對象實(shí)例,代碼如下:
打印一下結(jié)果:
是不是感覺沒什么毛???相信大多數(shù)人都是這樣使用的,也包括我。在單線程下自然沒毛病了,但是運(yùn)用到多線程下就有大問題了。
測試下:
控制臺打印結(jié)果:
你看結(jié)果,發(fā)現(xiàn)了什么?直接崩了,部分線程獲取的時間不對,部分線程報java.lang.NumberFormatException:multiple points錯,線程直接掛死了。還有部分線程報empty String錯,值有問題。
多線程不安全原因
因?yàn)槲覀儼裇impleDateFormat定義為靜態(tài)變量,那么多線程下SimpleDateFormat的實(shí)例就會被多個線程共享,B線程會讀取到A線程的時間,就會出現(xiàn)時間差異和其它各種問題。SimpleDateFormat和它繼承的DateFormat類也不是線程安全的。
來看看SimpleDateFormat的format()方法的源碼:
注意, calendar.setTime(date),SimpleDateFormat的format方法實(shí)際操作的就是Calendar。
因?yàn)槲覀兟暶鱏impleDateFormat為static變量,那么它的Calendar變量也就是一個共享變量,可以被多個線程訪問。
假設(shè)線程A執(zhí)行完calendar.setTime(date),把時間設(shè)置成2019-01-02,這時候被掛起,線程B獲得CPU執(zhí)行權(quán)。線程B也執(zhí)行到了calendar.setTime(date),把時間設(shè)置為2019-01-03。線程掛起,線程A繼續(xù)走,calendar還會被繼續(xù)使用(subFormat方法),而這時calendar用的是線程B設(shè)置的值了,而這就是引發(fā)問題的根源,出現(xiàn)時間不對,線程掛死等等。
其實(shí)SimpleDateFormat源碼上作者也給過我們提示:
翻譯過來的意思就是:
日期格式未同步。
建議為每個線程創(chuàng)建多帶帶的格式實(shí)例。
如果多個線程同時訪問格式,則必須在外部同步
解決方案
只在需要的時候創(chuàng)建新實(shí)例,不用static修飾。
如上代碼,僅在需要用到的地方創(chuàng)建一個新的實(shí)例,就沒有線程安全問題,不過也加重了創(chuàng)建對象的負(fù)擔(dān),會頻繁地創(chuàng)建和銷毀對象,效率較低。
采用Synchronized方式
簡單粗暴,synchronized往上一套也可以解決線程安全問題,缺點(diǎn)自然就是并發(fā)量大的時候會對性能有影響,線程阻塞。
ThreadLocal
ThreadLocal可以確保每個線程都可以得到多帶帶的一個SimpleDateFormat的對象,那么自然也就不存在競爭問題了。
基于JDK1.8的DateTimeFormatter
也是《阿里巴巴開發(fā)手冊》給我們的解決方案,對之前的代碼進(jìn)行改造:
運(yùn)行結(jié)果就不貼了,不會出現(xiàn)報錯和時間不準(zhǔn)確的問題。
DateTimeFormatter源碼上作者也加注釋說明了,他的類是不可變的,并且是線程安全的。
OK,現(xiàn)在是不是可以對你項(xiàng)目里的日期工具類進(jìn)行一波優(yōu)化了呢?
知識擴(kuò)展
在上述代碼中,我們通過創(chuàng)建一個線程池,來實(shí)現(xiàn)多線程循環(huán)打印日期的操作,但是我們創(chuàng)建方式你有沒有留意。
ExecutorService?executorService?=?Executors.newFixedThreadPool(100);
當(dāng)你IDEA安裝了阿里巴巴的代碼規(guī)范檢查插件時,使用Executors來創(chuàng)建線程池的話,會出現(xiàn)提示讓你手動創(chuàng)建線程池。
因此,我們可以將創(chuàng)建線程池的代碼改成:
ExecutorService?executorService?=?new?ThreadPoolExecutor(100,?100,0L,?TimeUnit.MILLISECONDS,?new?LinkedBlockingQueue<>());
但是又會有提示,建議要為線程池中的線程設(shè)置名稱:
改造之后的代碼為:
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可采取其他方式設(shè)置線程名稱,也可用其他方式手動創(chuàng)建線程池。
為什么要這樣做
我們參考阿里巴巴的Java開發(fā)手冊內(nèi)容:
關(guān)于Executors
關(guān)于線程名稱
再次簡單進(jìn)一步解讀下:
newFixedThreadPool和newSingleThreadExecutor 由于最后一個參數(shù)即工作隊(duì)列是
鏈表類型的阻塞隊(duì)列,而我們看其構(gòu)造函數(shù)發(fā)現(xiàn),默認(rèn)隊(duì)列大小是整數(shù)的最大值?。?!
所以如果請求太多,隊(duì)列很可能就耗費(fèi)內(nèi)存非常大導(dǎo)致OOM。
但是他們的線程數(shù)是固定的,而且一般不會太大,所以不會因?yàn)閯?chuàng)建過多線程而導(dǎo)致OOM。
再來看下newCachedThreadPool和newScheduledThreadPool
其中第最大線程池大小是整數(shù)的最大值,因此線程可能不斷創(chuàng)建,乃至到整數(shù)的最大值個線程,很容易導(dǎo)致OOM。其中工作隊(duì)列使用的是 SynchronousQueue
A {@linkplain BlockingQueue blocking queue} in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.
該類型的阻塞隊(duì)列每一個插入操作必須等待對應(yīng)的元素被另一個線程所移除,反之亦然。
因此阻塞隊(duì)列不會無限拓展而導(dǎo)致OOM。
當(dāng)我們學(xué)習(xí)和理解一些原則的同時,多注重源碼分析?。。?
·END·
程序員的成長之路
路雖遠(yuǎn),行則必至
本文原發(fā)于 同名微信公眾號「程序員的成長之路」,回復(fù)「1024」你懂得,給個贊唄。
微信ID:cxydczzl
往期精彩回顧
程序員接私活的7大平臺利器
教你一招用 IDE 編程提升效率的騷操作!
大學(xué)期間的副業(yè)賺錢之道
一個對話讓你明白架構(gòu)師是做什么的?
作為程序員的你,一年看幾本技術(shù)相關(guān)的書
5個相見恨晚的Linux命令
為啥程序員下班后只關(guān)顯示器從不關(guān)電腦?
送給程序員們的經(jīng)典電子書大禮包
面試時如何優(yōu)雅地自我介紹?
支撐百萬并發(fā)的數(shù)據(jù)庫架構(gòu)如何設(shè)計(jì)?
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/73800.html
摘要:保存中文上面我們的例子保存的是英文字符,下面我們來看下保存中文字符會怎么樣。出異常了中文屬于字符,英文數(shù)據(jù)字符,中文占個字符或者個字符,英文占個字符。如果為,則表示刪除該。的值規(guī)定為域名的隱私安全機(jī)制決定是不可跨域名的。 什么是會話技術(shù) 基本概念: 指用戶開一個瀏覽器,訪問一個網(wǎng)站,只要不關(guān)閉該瀏覽器,不管該用戶點(diǎn)擊多少個超鏈接,訪問多少資源,直到用戶關(guān)閉瀏覽器,整個這個過程我們稱為一...
摘要:什么是在對的解釋如下是一個用來對位置敏感的格式化和解析日期的實(shí)體類。他允許把日期格式化成,把解析成日期和規(guī)范化。 1.什么是SimpleDateFormat 在java doc對SimpleDateFormat的解釋如下: SimpleDateFormat is a concrete class for formatting and parsing dates in a locale-...
摘要:包裝類基本數(shù)據(jù)類型如等。它們并不具備對象的特性,比如不能調(diào)用方法。為了讓基本數(shù)據(jù)類型也能具有對象的特性,為每個基本數(shù)據(jù)類型提供了包裝類。 包裝類 基本數(shù)據(jù)類型:如 int、float、double、boolean、char 等。它們并不具備對象的特性,比如不能調(diào)用方法。為了讓基本數(shù)據(jù)類型也能具有對象的特性,java為每個基本數(shù)據(jù)類型提供了包裝類。 基本類型和包裝類之間的對應(yīng)關(guān)系: sh...
閱讀 1743·2023-04-25 19:37
閱讀 1316·2021-11-16 11:45
閱讀 2815·2021-10-18 13:30
閱讀 2776·2021-09-29 09:34
閱讀 1643·2019-08-30 15:55
閱讀 3121·2019-08-30 11:10
閱讀 1840·2019-08-29 16:52
閱讀 1006·2019-08-29 13:18