摘要:線程安全問題都是由全局變量及靜態(tài)變量引起的。常量始終是線程安全的,因?yàn)橹淮嬖谧x操作。局部變量是線程安全的。有狀態(tài)對(duì)象,就是有實(shí)例變量的對(duì)象,可以保存數(shù)據(jù),是非線程安全的。
前言
有多少人在使用Spring框架時(shí),很多時(shí)候不知道或者忽視了多線程的問題?
??因?yàn)閷懗绦驎r(shí),或做單元測(cè)試時(shí),很難有機(jī)會(huì)碰到多線程的問題,因?yàn)闆]有那么容易模擬多線程測(cè)試的環(huán)境。那么當(dāng)多個(gè)線程調(diào)用同一個(gè)bean的時(shí)候就會(huì)存在線程安全問題。如果是Spring中bean的創(chuàng)建模式為非單例的,也就不存在這樣的問題了。
??但如果不去考慮潛在的漏洞,它就會(huì)變成程序的隱形殺手,在你不知道的時(shí)候爆發(fā)。而且,通常是程序交付使用時(shí),在生產(chǎn)環(huán)境下觸發(fā),會(huì)是很麻煩的事。
??我們知道在一般情況下,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)采用ThreadLocal進(jìn)行處理,讓它們也成為線程安全的狀態(tài),因?yàn)橛袪顟B(tài)的Bean就可以在多線程中共享了。
??一般的Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層三個(gè)層次,在不同的層中編寫對(duì)應(yīng)的邏輯,下層通過接口向上層開放功能調(diào)用。在一般情況下,從接收請(qǐng)求到返回響應(yīng)所經(jīng)過的所有程序調(diào)用都同屬于一個(gè)線程
??ThreadLocal是解決線程安全問題一個(gè)很好的思路,它通過為每個(gè)線程提供一個(gè)獨(dú)立的變量副本解決了變量并發(fā)訪問的沖突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問題更簡(jiǎn)單,更方便,且結(jié)果程序擁有更高的并發(fā)性。
??如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。 或者說:一個(gè)類或者程序所提供的接口對(duì)于線程來說是原子操作或者多個(gè)線程之間的切換不會(huì)導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。線程安全問題都是由全局變量及靜態(tài)變量引起的。
??若每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
1) 常量始終是線程安全的,因?yàn)橹淮嬖谧x操作。
2)每次調(diào)用方法前都新建一個(gè)實(shí)例是線程安全的,因?yàn)椴粫?huì)訪問共享的資源。
3)局部變量是線程安全的。因?yàn)槊繄?zhí)行一個(gè)方法,都會(huì)在獨(dú)立的空間創(chuàng)建局部變量,它不是共享的資源。局部變量包括方法的參數(shù)變量和方法內(nèi)變量。
??有狀態(tài)就是有數(shù)據(jù)存儲(chǔ)功能。有狀態(tài)對(duì)象(Stateful Bean),就是有實(shí)例變量的對(duì)象 ,可以保存數(shù)據(jù),是非線程安全的。在不同方法調(diào)用間不保留任何狀態(tài)。
??無狀態(tài)就是一次操作,不能保存數(shù)據(jù)。無狀態(tài)對(duì)象(Stateless Bean),就是沒有實(shí)例變量的對(duì)象 .不能保存數(shù)據(jù),是不變類,是線程安全的。
有狀態(tài)對(duì)象:
??無狀態(tài)的Bean適合用不變模式,技術(shù)就是單例模式,這樣可以共享實(shí)例,提高性能。有狀態(tài)的Bean,多線程環(huán)境下不安全,那么適合用Prototype原型模式。Prototype: 每次對(duì)bean的請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。
??Struts2默認(rèn)的實(shí)現(xiàn)是Prototype模式。也就是每個(gè)請(qǐng)求都新生成一個(gè)Action實(shí)例,所以不存在線程安全問題。需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域
??SimpleDateFormat(?下面簡(jiǎn)稱?sdf)?類內(nèi)部有一個(gè)?Calendar?對(duì)象引用?,?它用來儲(chǔ)存和這個(gè)?sdf?相關(guān)的日期信息?,?例如?sdf.parse(dateStr),?sdf.format(date)??諸如此類的方法參數(shù)傳入的日期相關(guān)?String,?Date?等等?,??都是交友?Calendar?引用來儲(chǔ)存的?.?這樣就會(huì)導(dǎo)致一個(gè)問題?,?如果你的?sdf?是個(gè)?static?的?,??那么多個(gè)?thread??之間就會(huì)共享這個(gè)?sdf,?同時(shí)也是共享這個(gè)?Calendar?引用?,??并且?,??觀察??sdf.parse()??方法?,?你會(huì)發(fā)現(xiàn)有如下的調(diào)用?:
Date parse() { calendar.clear(); // 清理calendar ... // 執(zhí)行一些操作, 設(shè)置 calendar 的日期什么的 calendar.getTime(); // 獲取calendar的時(shí)間 }
??這里會(huì)導(dǎo)致的問題就是?,??如果?線程?A??調(diào)用了??sdf.parse(),??并且進(jìn)行了?calendar.clear()?后還未執(zhí)行?calendar.getTime()?的時(shí)候?,?線程?B?又調(diào)用了?sdf.parse(),?這時(shí)候線程?B?也執(zhí)行了?sdf.clear()?方法?,??這樣就導(dǎo)致線程?A?的的?calendar?數(shù)據(jù)被清空了?(?實(shí)際上?A,B?的同時(shí)被清空了?).??又或者當(dāng)??A??執(zhí)行了?calendar.clear()??后被掛起?,??這時(shí)候?B??開始調(diào)用?sdf.parse()?并順利?i?結(jié)束?,??這樣??A??的??calendar?內(nèi)存儲(chǔ)的的?date?變成了后來?B?設(shè)置的?calendar?的?date
??這個(gè)問題背后隱藏著一個(gè)更為重要的問題?--?無狀態(tài):無狀態(tài)方法的好處之一,就是它在各種環(huán)境下,都可以安全的調(diào)用。衡量一個(gè)方法是否是有狀態(tài)的,就看它是否改動(dòng)了其它的東西,比如全局變量,比如實(shí)例的字段。?format?方法在運(yùn)行過程中改動(dòng)了SimpleDateFormat?的?calendar?字段,所以,它是有狀態(tài)的。
??這也同時(shí)提醒我們?cè)陂_發(fā)和設(shè)計(jì)系統(tǒng)的時(shí)候注意下以下三點(diǎn)?:
自己寫公用類的時(shí)候,要對(duì)多線程調(diào)用情況下的后果在注釋里進(jìn)行明確說明
對(duì)線程環(huán)境下,對(duì)每一個(gè)共享的可變變量都要注意其線程安全性
我們的類和方法在做設(shè)計(jì)的時(shí)候,要盡量設(shè)計(jì)成無狀態(tài)的
??說明:在需要用到?SimpleDateFormat??的地方新建一個(gè)實(shí)例,不管什么時(shí)候,將有線程安全問題的對(duì)象由共享變?yōu)榫植克接卸寄鼙苊舛嗑€程問題,不過也加重了創(chuàng)建對(duì)象的負(fù)擔(dān)。在一般情況下,這樣其實(shí)對(duì)性能影響比不是很明顯的。
public class DateSyncUtil { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String formatDate(Date date)throws ParseException{ synchronized(sdf){ return sdf.format(date); } } public static Date parse(String strDate) throws ParseException{ synchronized(sdf){ return sdf.parse(strDate); } } }
??說明:當(dāng)線程較多時(shí),當(dāng)一個(gè)線程調(diào)用該方法時(shí),其他想要調(diào)用此方法的線程就要block ,多線程并發(fā)量大的時(shí)候會(huì)對(duì)性能有一定的影響。
public class ConcurrentDateUtil { private static ThreadLocalthreadLocal = new ThreadLocal () { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static Date parse(String dateStr) throws ParseException { return threadLocal.get().parse(dateStr); } public static String format(Date date) { return threadLocal.get().format(date); } }
??或
ThreadLocal(); public static DateFormat getDateFormat() { DateFormat df = threadLocal.get(); if(df==null){ df = new SimpleDateFormat(date_format); threadLocal.set(df); } return df; } public static String formatDate(Date date) throws ParseException { return getDateFormat().format(date); } public static Date parse(String strDate) throws ParseException { return getDateFormat().parse(strDate); } }
??說明:使用?ThreadLocal,??也是將共享變量變?yōu)楠?dú)享,線程獨(dú)享肯定能比方法獨(dú)享在并發(fā)環(huán)境中能減少不少創(chuàng)建對(duì)象的開銷。如果對(duì)性能要求比較高的情況下,一般推薦使用這種方法。
使用?Apache?commons??里的?FastDateFormat?,宣稱是既快又線程安全的SimpleDateFormat,??可惜它只能對(duì)日期進(jìn)行?format,??不能對(duì)日期串進(jìn)行解析。
使用?Joda-Time?類庫來處理時(shí)間相關(guān)問題
??做一個(gè)簡(jiǎn)單的壓力測(cè)試,方法一最慢,方法三最快,但是就算是最慢的方法一性能也不差,一般系統(tǒng)方法一和方法二就可以滿足,所以說在這個(gè)點(diǎn)很難成為你系統(tǒng)的瓶頸所在。從簡(jiǎn)單的角度來說,建議使用方法一或者方法二,如果在必要的時(shí)候,追求那么一點(diǎn)性能提升的話,可以考慮用方法三,用?ThreadLocal?做緩存。
??Joda-Time?類庫對(duì)時(shí)間處理方式比較完美,建議使用。
總結(jié)??回到文章開頭的問題:《有多少人在使用Spring框架時(shí),很多時(shí)候不知道或者忽視了多線程的問題?》
??其實(shí)代碼誰都會(huì)寫,為什么架構(gòu)師寫的代碼效果和你的天差地別呢?應(yīng)該就是此類你沒考慮到的小問題而架構(gòu)師都考慮到了。
??架構(gòu)師知識(shí)面更廣,見識(shí)到的具體情況更多,解決各類問題的經(jīng)驗(yàn)更豐富。只要你養(yǎng)成架構(gòu)師的思維和習(xí)慣,那你離架構(gòu)師還會(huì)遠(yuǎn)嗎?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/11415.html
摘要:線程安全問題都是由全局變量及靜態(tài)變量引起的。常量始終是線程安全的,因?yàn)橹淮嬖谧x操作。局部變量是線程安全的。有狀態(tài)對(duì)象,就是有實(shí)例變量的對(duì)象,可以保存數(shù)據(jù),是非線程安全的。 前言 有多少人在使用Spring框架時(shí),很多時(shí)候不知道或者忽視了多線程的問題? ??因?yàn)閷懗绦驎r(shí),或做單元測(cè)試時(shí),很難有機(jī)會(huì)碰到多線程的問題,因?yàn)闆]有那么容易模擬多線程測(cè)試的環(huán)境。那么當(dāng)多個(gè)線程調(diào)用同一個(gè)bean的時(shí)...
摘要:本文會(huì)以引出問題為主,后面有時(shí)間的話,筆者陸續(xù)會(huì)抽些重要的知識(shí)點(diǎn)進(jìn)行詳細(xì)的剖析與解答。敬請(qǐng)關(guān)注服務(wù)端思維微信公眾號(hào),獲取最新文章。 原文地址:梁桂釗的博客博客地址:http://blog.720ui.com 這里,筆者結(jié)合自己過往的面試經(jīng)驗(yàn),整理了一些核心的知識(shí)清單,幫助讀者更好地回顧與復(fù)習(xí) Java 服務(wù)端核心技術(shù)。本文會(huì)以引出問題為主,后面有時(shí)間的話,筆者陸續(xù)會(huì)抽些重要的知識(shí)點(diǎn)進(jìn)...
摘要:作者重慶森林鏈接來源??途W(wǎng)整個(gè)三月份通過??途W(wǎng)和網(wǎng)友分享的經(jīng)驗(yàn)學(xué)到了很多東西,現(xiàn)在反饋一下我的面試經(jīng)歷,希望對(duì)同學(xué)們有幫助。個(gè)人情況大三本方向渣碩,經(jīng)過實(shí)驗(yàn)室學(xué)長(zhǎng)內(nèi)推,于三月底完成面試。校招是實(shí)力和運(yùn)氣的結(jié)合,缺一不可。 歡迎關(guān)注我的微信公眾號(hào):Java面試通關(guān)手冊(cè)(堅(jiān)持原創(chuàng),分享美文,分享各種Java學(xué)習(xí)資源,面試題,以及企業(yè)級(jí)Java實(shí)戰(zhàn)項(xiàng)目回復(fù)關(guān)鍵字免費(fèi)領(lǐng)取):showImg(h...
摘要:以下為大家整理了阿里巴巴史上最全的面試題,涉及大量面試知識(shí)點(diǎn)和相關(guān)試題。的內(nèi)存結(jié)構(gòu),和比例。多線程多線程的幾種實(shí)現(xiàn)方式,什么是線程安全。點(diǎn)擊這里有一套答案版的多線程試題。線上系統(tǒng)突然變得異常緩慢,你如何查找問題。 以下為大家整理了阿里巴巴史上最全的 Java 面試題,涉及大量 Java 面試知識(shí)點(diǎn)和相關(guān)試題。 JAVA基礎(chǔ) JAVA中的幾種基本數(shù)據(jù)類型是什么,各自占用多少字節(jié)。 S...
摘要:是的簡(jiǎn)稱,運(yùn)行環(huán)境,為的運(yùn)行提供了所需的環(huán)境。分割字符串,返回分割后的字符串?dāng)?shù)組。當(dāng)計(jì)算的值相同時(shí),我們稱之為沖突,的做法是用鏈表和紅黑樹存儲(chǔ)相同的值的。迭代器取代了集合框架中的,迭代器允許調(diào)用者在迭代過程中移除元素。 Java基礎(chǔ)1.JDK和JRE有什么區(qū)別? JDK 是java development kit的簡(jiǎn)稱,java開發(fā)工具包,提供java的開發(fā)環(huán)境和運(yùn)行環(huán)境。JRE 是j...
閱讀 1345·2021-11-15 11:37
閱讀 2222·2021-09-23 11:21
閱讀 1307·2019-08-30 15:55
閱讀 2115·2019-08-30 15:55
閱讀 2823·2019-08-30 15:52
閱讀 2830·2019-08-30 11:12
閱讀 1583·2019-08-29 18:45
閱讀 1895·2019-08-29 14:04