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