【轉】Spring Bean單例與線程安全


  

一、Spring單例模式及線程安全

  Spring框架中的Bean,或者說組件,獲取實例的時候都是默認單例模式,這是在多線程開發的時候需要尤其注意的地方。

  單例模式的意思是只有一個實例,例如在Spring容器中某一個類只有一個實例,而且自行實例化后並項整個系統提供這個實例,這個類稱為單例類。

  當多個用戶同時請求一個服務時,容器會給每一個請求分配一個線程,這時多個線程會並發執行該請求對應的業務邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對單例狀態的修改(體現為該單例的成員屬性),則必須考慮線程同步問題。

同步機制的比較:

  ThreadLocal和線程同步機制相比有什么優勢呢?他們都是為了解決多線程中相同變量的訪問沖突問題。

  在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。 
 
  而ThreadLocal則從另一個角度來解決多線程的並發訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。 
 
  由於ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用
 概括起來說,對於多線程資源共享的問題, 同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。 
 
  Spring使用ThreadLocal解決線程安全問題 
 
  我們知道在一般情況下,只有無狀態的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引用,非線程安全的。


  這個問題背后隱藏着一個更為重要的問題--無狀態:無狀態方法的好處之一,就是它在各種環境下,都可以安全的調用。衡量一個方法是否是有狀態的,就看它是否改動了其它的東西,比如全局變量,比如實例的字段。format方法在運行過程中改動了SimpleDateFormat的calendar字段,所以,它是有狀態的。

  這也同時提醒我們在開發和設計系統的時候注意下一下三點:
  1.自己寫公用類的時候,要對多線程調用情況下的后果在注釋里進行明確說明
  2.對線程環境下,對每一個共享的可變變量都要注意其線程安全性
  3.我們的類和方法在做設計的時候,要盡量設計成無狀態的
  解決方法:

  1.使用synchronized關鍵字進行數據同步,或者使用ThreadLocal保證線程安全

  2.不適用JDK自帶的時間格式化類,使用其他類庫的

    •   使用Apache commons里的FastDateFormat,宣城是既快有線程安全的SimpleDateFormat,可惜他只能對日期進行format,不能對日期串進行解析
    •   使用Joda-Time類庫來處理時間相關問題,該種對時間的處理方式比較完美,建議使用

  原文鏈接:https://www.cnblogs.com/redcool/p/6398760.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM