一、Spring單例模式及線程安全
Spring框架中的Bean,或者說組件屁魏,獲取實例的時候都是默認單例模式右蹦,這是在多線程開發(fā)的時候需要尤其注意的地方诊杆。
單例模式的意思是只有一個實例,例如在Spring容器中某一個類只有一個實例何陆,而且自行實例化后并項整個系統(tǒng)提供這個實例刽辙,這個類稱為單例類。
當多個用戶同時請求一個服務時甲献,容器會給每一個請求分配一個線程宰缤,這時多個線程會并發(fā)執(zhí)行該請求對應的業(yè)務邏輯(成員方法),此時就要注意了晃洒,如果該處理邏輯中有對單例狀態(tài)的修改(體現(xiàn)為該單例的成員屬性)慨灭,則必須考慮線程同步問題。
同步機制的比較:
ThreadLocal和線程同步機制相比有什么優(yōu)勢呢球及?他們都是為了解決多線程中相同變量的訪問沖突問題氧骤。
在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量吃引。這時該變量是多個線程共享的筹陵,使用同步機制要求程序慎密地分析什么時候?qū)ψ兞窟M行讀寫,什么時候需要鎖定某個對象镊尺,什么時候釋放對象鎖等繁雜的問題朦佩,程序設計和編寫難度相對較大。?
而ThreadLocal則從另一個角度來解決多線程的并發(fā)訪問庐氮。ThreadLocal會為每一個線程提供一個獨立的變量副本语稠,從而隔離了多個線程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了仙畦。ThreadLocal提供了線程安全的共享對象输涕,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal慨畸。?
由于ThreadLocal中可以持有任何類型的對象莱坎,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉(zhuǎn)換寸士。但JDK 5.0通過泛型很好的解決了這個問題型奥,在一定程度地簡化ThreadLocal的使用
概括起來說,對于多線程資源共享的問題碉京,同步機制采用了“以時間換空間”的方式厢汹,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量谐宙,讓不同的線程排隊訪問烫葬,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響凡蜻。
Spring使用ThreadLocal解決線程安全問題?
我們知道在一般情況下搭综,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中划栓,絕大部分Bean都可以聲明為singleton作用域兑巾。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager忠荞、LocaleContextHolder等)中非線程安全狀態(tài)采用ThreadLocal進行處理蒋歌,讓它們也成為線程安全的狀態(tài),因為有狀態(tài)的Bean就可以在多線程中共享了委煤。
一般的Web應用劃分為展現(xiàn)層堂油、服務層和持久層三個層次,在不同的層中編寫對應的邏輯碧绞,下層通過接口向上層開放功能調(diào)用府框。在一般情況下,從接收請求到返回響應所經(jīng)過的所有程序調(diào)用都同屬于一個線程
ThreadLocal是解決線程安全問題一個很好的思路讥邻,它通過為每個線程提供一個獨立的變量副本解決了變量并發(fā)訪問的沖突問題迫靖。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單兴使,更方便系宜,且結(jié)果程序擁有更高的并發(fā)性。
如果你的代碼所在的進程中有多個線程在同時運行鲫惶,而這些線程可能會同時運行這段代碼蜈首。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的实抡,而且其他的變量的值也和預期的是一樣的欠母,就是線程安全的欢策。 或者說:一個類或者程序所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。 線程安全問題都是由全局變量及靜態(tài)變量引起的赏淌。
若每個線程中對全局變量踩寇、靜態(tài)變量只有讀操作,而無寫操作六水,一般來說俺孙,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作掷贾,一般都需要考慮線程同步睛榄,否則就可能影響線程安全。
1) 常量始終是線程安全的想帅,因為只存在讀操作场靴。
2)每次調(diào)用方法前都新建一個實例是線程安全的,因為不會訪問共享的資源港准。
3)局部變量是線程安全的旨剥。因為每執(zhí)行一個方法,都會在獨立的空間創(chuàng)建局部變量浅缸,它不是共享的資源轨帜。局部變量包括方法的參數(shù)變量和方法內(nèi)變量。
有狀態(tài)就是有數(shù)據(jù)存儲功能衩椒。有狀態(tài)對象(Stateful Bean)蚌父,就是有實例變量的對象? ,可以保存數(shù)據(jù)毛萌,是非線程安全的梢什。在不同方法調(diào)用間不保留任何狀態(tài)。
無狀態(tài)就是一次操作朝聋,不能保存數(shù)據(jù)嗡午。無狀態(tài)對象(Stateless Bean),就是沒有實例變量的對象? .不能保存數(shù)據(jù)冀痕,是不變類荔睹,是線程安全的。
有狀態(tài)對象:
無狀態(tài)的Bean適合用不變模式言蛇,技術就是單例模式僻他,這樣可以共享實例,提高性能腊尚。有狀態(tài)的Bean吨拗,多線程環(huán)境下不安全,那么適合用Prototype原型模式。Prototype: 每次對bean的請求都會創(chuàng)建一個新的bean實例劝篷。
Struts2默認的實現(xiàn)是Prototype模式哨鸭。也就是每個請求都新生成一個Action實例,所以不存在線程安全問題娇妓。需要注意的是像鸡,如果由Spring管理action的生命周期, scope要配成prototype作用域
SimpleDateFormat(下面簡稱sdf)類內(nèi)部有一個Calendar對象引用,它用來儲存和這個sdf相關的日期信息,例如sdf.parse(dateStr), sdf.format(date) 諸如此類的方法參數(shù)傳入的日期相關String, Date等等, 都是交友Calendar引用來儲存的.這樣就會導致一個問題,如果你的sdf是個static的, 那么多個thread 之間就會共享這個sdf, 同時也是共享這個Calendar引用只估,非線程安全的。
這個問題背后隱藏著一個更為重要的問題--無狀態(tài):無狀態(tài)方法的好處之一着绷,就是它在各種環(huán)境下蛔钙,都可以安全的調(diào)用。衡量一個方法是否是有狀態(tài)的荠医,就看它是否改動了其它的東西夸楣,比如全局變量,比如實例的字段子漩。format方法在運行過程中改動了SimpleDateFormat的calendar字段豫喧,所以,它是有狀態(tài)的幢泼。
這也同時提醒我們在開發(fā)和設計系統(tǒng)的時候注意下一下三點:
1.自己寫公用類的時候紧显,要對多線程調(diào)用情況下的后果在注釋里進行明確說明
2.對線程環(huán)境下,對每一個共享的可變變量都要注意其線程安全性
3.我們的類和方法在做設計的時候缕棵,要盡量設計成無狀態(tài)的
解決方法:
1.使用synchronized關鍵字進行數(shù)據(jù)同步孵班,或者使用ThreadLocal保證線程安全
2.不適用JDK自帶的時間格式化類,使用其他類庫的
使用Apache commons里的FastDateFormat招驴,宣城是既快有線程安全的SimpleDateFormat翰灾,可惜他只能對日期進行format黎做,不能對日期串進行解析
使用Joda-Time類庫來處理時間相關問題巩步,該種對時間的處理方式比較完美哩俭,建議使用。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一触趴、單例模式:在spring中其實是scope(作用范圍)參數(shù)的缺省設定值
每個bean定義只生成一個對象實例,每次getBean請求獲得的都是此實例
單例模式分為餓漢模式和懶漢模式氮发,
餓漢模式spring singleton的缺省是餓漢模式:啟動容器時(即實例化容器時),為所有spring配置文件中定義的bean都生成一個實例
懶漢模式在第一個請求時才生成一個實例,以后的請求都調(diào)用這個實例
----------------------------------------------
spring singleton設置為懶漢模式:
default-lazy-init="true">
------------------------------------------------
二、另一種和singleton對應的scope值---prototype多實例模式
調(diào)用getBean時,就new一個新實例
--------------------------------------------------------
singleton和prototype的比較
xml配置文件:
測試代碼:
ctx = new ClassPathXmlApplicationContext("spring-hibernate-mysql.xml");
DvdTypeDAO tDao1 = (DvdTypeDAO)ctx.getBean("dvdTypeDAO");
DvdTypeDAO tDao2 = (DvdTypeDAO)ctx.getBean("dvdTypeDAO");
運行:
-----------------------------------------
true
com.terana.hibernate.impl.DvdTypeDAOImpl@15b0333
com.terana.hibernate.impl.DvdTypeDAOImpl@15b0333
--------------------------------------
說明前后兩次getBean()獲得的是同一實例,說明spring缺省是單例
prototype:
<bean id="dvdTypeDAO" class="com.terana.hibernate.impl.DvdTypeDAOImpl"?scope="prototype"?/>
執(zhí)行同樣的測試代碼
----------------------------------------------------
運行:
false
com.terana.hibernate.impl.DvdTypeDAOImpl@afae4a
com.terana.hibernate.impl.DvdTypeDAOImpl@1db9852
說明scope="prototype"后,每次getBean()的都是不同的新實例
---------------------------------------------------------
轉(zhuǎn)自https://blog.csdn.net/qq_35661171/article/details/83180546