Spring框架里的Bean,默認為單例模式,這是在多線程開發(fā)的時候要尤其注意的地方。
當多用戶同時請求一個服務(wù)時,容器會給每一個請求分配一個線程,這是多個線程會并發(fā)執(zhí)行該請求多對應(yīng)的業(yè)務(wù)邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對該單例Bean狀態(tài)的修改(體現(xiàn)為該單例Bean的成員屬性),則必須考慮線程同步問題。
Spring使用ThreadLocal解決線程安全問題
ThreadLocal和線程同步機制相比有什么優(yōu)勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。
在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候?qū)ψ兞窟M行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設(shè)計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多線程的并發(fā)訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。
由于ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉(zhuǎn)換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用,概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
我們知道在一般情況下,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如TransactionSynchronizationManager、LocaleContextHolder、RequestContextHolder、Hibernate的AnnotationSessionFactoryBean等)中非線程安全狀態(tài)采用ThreadLocal進行處理,讓它們也成為線程安全的狀態(tài),因為有狀態(tài)的Bean就可以在多線程中共享了。
一般的Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層三個層次,在不同的層中編寫對應(yīng)的邏輯,下層通過接口向上層開放功能調(diào)用。在一般情況下,從接收請求到返回響應(yīng)所經(jīng)過的所有程序調(diào)用都同屬于一個線程
ThreadLocal是解決線程安全問題一個很好的思路,它通過為每個線程提供一個獨立的變量副本解決了變量并發(fā)訪問的沖突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結(jié)果程序擁有更高的并發(fā)性。
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。 或者說:一個類或者程序所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。 線程安全問題都是由全局變量及靜態(tài)變量引起的。
若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
線程安全的情況
- 常量始終是線程安全的,因為只存在讀操作。
- 每次調(diào)用方法前都新建一個實例是線程安全的,因為不會訪問共享的資源。
- 局部變量是線程安全的。因為每執(zhí)行一個方法,都會在獨立的空間創(chuàng)建局部變量,它不是共享的資源。局部變量包括方法的參數(shù)變量和方法內(nèi)變量。
有狀態(tài)與無狀態(tà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)的Bean適合用不變模式,技術(shù)就是單例模式,這樣可以共享實例,提高性能。
有狀態(tài)的Bean,多線程環(huán)境下不安全,那么適合用Prototype原型模式。Prototype: 每次對bean的請求都會創(chuàng)建一個新的Bean實例。