Spring創建好的單例對象是否存在線程安全的問題?

前言:

這是我在一次面試中被問到過的問題,但是當時我回答的並不是太好,最近在學習多線程知識的時候又對這個問題有了新的理解,所以這篇文章主要講解下我對個問題的理解。

正文:

一、理解這個問題前,你需要先知道幾個知識點

1.spring的bean作用域都有哪些?默認是哪個?

作用域 字符 描述
單例 singleton 整個應用中只創建一個實例
原型 prototype 每次注入時都新建一個實例
會話 request 爲每個會話創建一個實例
請求 session 爲每個請求創建一個實例

 

 

 

 

 

默認的是:單例 singleton

2.創建單例的方式是否線程安全使用已經創建好的單例對象是否線程安全是兩個問題

①常見創建單例的方式懶漢式和餓漢式

懶漢式(不安全寫法)

public class Singleton{ 
    private Singleton(){}
    private static Singleton singleton = null;  //不建立對象
    public static Singleton getInstance(){
             if(singleton == null) {        //先判斷是否爲空
                 singleton = new Singleton ();  //懶漢式做法 
             }
             return singleton ;
     }
}

餓漢式

public class Singleton{ 
    public Singleton(){}
    private static Singleton singleton = new Singleton();  //建立對象
    public static Singleton getInstance(){
  return singleton ;//直接返回單例對象 }}

這兩種創建方式中,懶漢式在多線程環境下就是線程不安全的,假設有線程1和線程2兩個線程,線程1在判斷if(singleton == null)的時候,突然失去cpu的執行權,而線程2獲得了cpu的執行權,執行了getInstance()方法,創建了個對象,但是這個事情線程1並不知道,線程1重新獲得cpu的執行權時,判斷f(singleton == null)結果是null,所以又去創建了對象,那麼這樣就會出現破壞單例的情況,有多餘的對象,所以線程是不安全的,解決方案之一就是加鎖,代碼如下

懶漢式(安全寫法)

public class Singleton{ 
    private Singleton(){}
    private static Singleton singleton = null;  //不建立對象
    public static synchronized Singleton getInstance(){
             if(singleton == null) {        //先判斷是否爲空
                 singleton = new Singleton ();  //懶漢式做法 
             }
             return singleton ;
     }
}

②在spring的框架裏,對象是交給spring容器創建的,spring的創建單例的方式既不是懶漢式也不是餓漢式,是單例註冊表模式實現單例模式的,感興趣的可以看這篇文章:https://blog.csdn.net/u012794505/article/details/80926823,這種創建單例模式的方式是線程安全的。

③怎麼判斷使用已經創建好的單例對象是否線程安全

  • 看這個單例裏有沒有全局變量(全局變量就是成員變量,成員變量又分實例變量和靜態變量)
  • 如果有全局變量,看它是不是只可以讀取而不能寫入(有沒有發佈set方法)

如果滿足上面兩個條件,那麼這個單例就是不安全的。 

二、spring的單例模式與線程安全

1.spring框架裏的bean獲取實例的時候都是默認單例模式,所以在多線程開發裏就有可能會出現線程不安全的問題。當多個用戶同時請求一個服務器時,容器(tomcat)會給每一個請求分配一個線程,這時多個線程會併發執行該請求所對應的業務邏輯(controller裏的方法),此時就要注意啦,如果controller(是單例對象)裏有全局變量並且又是可以修改的,那麼就需要考慮線程安全的問題。解決方案有很多,比如設置@scope("prototype")爲多例模式,爲每個線程創建一個controller,還可以使用ThreadLocal。

2.其實spring的源碼裏比如RequestContextHolder、TransactionSynchronizationManager、LoxaleContextHolder等這些對象創建方式也是單例,底層就是用ThreadLocal處理的。ThreadLocal基本實現思路是:它會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突,因爲每個線程都擁有自己的變量副本,從而也就沒必要對該變量進行同步啦。

3.在ssh或ssm框架裏的service或dao對象雖然也是單例模式,但正如上面分析的,他們沒有可修改的全局變量,所以在多線程環境下也是安全的。

4.其實在很多文章中對於spring的單例模式與線程安全會提到一個概念有狀態對象和無狀態對象,無狀態對象在多線程環境下是線程安全的,有狀態的對象則不是,其實這個字面的意思是比較對的,因爲這個對象如果無法存儲數據,也就不會出現多個線程操作共享數據的情況,自然安全,概念如下

有狀態就是有數據存儲功能。有狀態對象(Stateful Bean),就是有實例變量的對象,可以保存數據,是非線程安全的。在不同方法調用間不保留任何狀態。

無狀態就是一次操作,不能保存數據。無狀態對象(Stateless Bean),就是沒有實例變量的對象.不能保存數據,是不變類,是線程安全的。

但是很多文章舉的無狀態對象的例子我認爲不合理。比如:https://blog.csdn.net/bingjing12345/article/details/9794945,因爲如果這個對象沒有set放方法只是可讀,其實也是安全的。

public class StatefulBean {  
  
    public int state;  
    // 由於多線程環境下,user是引用對象,是非線程安全的  
    public User user;  
  
    public int getState() {  
        return state;  
    }  
  
    public void setState(int state) {  
        this.state = state;  
    }  
  
    public User getUser() {  
        return user;  
    }  
  
    public void setUser(User user) {  
        this.user = user;  
    }  
}  

總結:

其實你越去了解框架底層的實現原理,你越會爲這個框架的思想而着迷,你會感慨框架筆者的想法是多麼的奇妙,多麼的周全,多一字嫌多,少一字嫌少。最後來句毒雞湯,如果你從此刻開始努力,最壞的結果,也是大氣晚成。我是阿達,一名喜歡分享知識的程序員,時不時的也會荒腔走板的聊一聊電影、電視劇、音樂、漫畫,這裏已經有41位小夥伴在等你們啦,感興趣的就趕緊來點擊關注我把,哪裏有不明白或有不同觀點的地方歡迎留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章