單例模式介紹及其線程安全問題


 

    介紹下單例模式,即保證對一個類只實例化一個對象。實際生產例子有,Spring的bean默認創建模式等。

  單例模式的組成:包括一個私有的構造器,一個私有的靜態變量,一個公有的靜態方法。單例模式本身很簡單,主要復雜點是在它在線程並發下的如何保證 線程安全+資源消耗少 的問題。

 

  一.餓漢式單例(線程安全)

 

缺點:直接實例化,資源會浪費。丟失了延遲實例化的性能好處。

 

 二.懶漢式單例(線程不安全)

 

 缺點:線程不安全,如果多個線程能夠同時進入 if (instance == null) ,並且此時 instance 為 null,那么會有多個線程執行 instance = new LazySingleton(); 語句,這將導致實例化多次對象。即此類不是單例了。

 

三.線程安全的懶漢式單例例子。

  1>  對 getInstance() 方法加鎖,那么在一個時間點只能有一個線程能夠進入該方法,從而避免了實例化多次 uniqueInstance。

缺點:會有線程堵塞,性能上不好,不推薦。

 

 

 2>  雙重校驗鎖。先判斷 instance 是否已經被實例化,如果沒有被實例化,那么才對實例化語句進行加鎖

缺點:基本無缺點,除了第一次實例化的時候會加鎖,可能會有線程堵塞。

  如果兩個線程都執行了 if 語句,那么兩個線程都會進入 if 語句塊內。雖然在 if 語句塊內有加鎖操作,但是兩個線程都會執行instance = new Singleton(); 這條語句,只是先后的問題,那么就會進行兩次實例化。因此必須使用雙重校驗鎖,也就是需要使用兩個 if 語句。

  instance 采用 volatile 關鍵字修飾也是很有必要的。volatile的作用是 1.內存可見性: 所有線程都能看到共享內存的最新狀態  2.防止指令重排。 (volatile修飾的變量並不是原子變量。只是變量的讀和寫變成了原子操作)

   instance = new LazySingleton(); 這段代碼其實是分為三步執行:

  1. 為 instance 分配內存空間
  2. 初始化 instance 
  3. 將 instance 指向分配的內存地址

但是由於 JVM 具有指令重排的特性,執行順序有可能變成 1>3>2。指令重排在單線程環境下不會出現問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。例如,線程 T1 執行了 1 和 3,此時 T2 調用 getInstance() 后發現 instance 不為空,因此返回 instance ,但此時 instance 還未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環境下也能正常運行。

 

 

 3>靜態內部類實現。具有延遲初始化的好處,而且由 JVM 提供了對線程安全的支持。

內部靜態類的變量並不會在類加載的時候就初始化好,而是會在調用的時候才初始化。保證資源的節約。

 


免責聲明!

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



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