介紹創建Java單例對象的七種方式,重點掌握哪些創建方式是線程安全的,哪些方式是線程不安全的,並能夠在實際項目中靈活運用設計模式,編寫可維護的代碼。 ...
本文分享自華為雲社區《《Java極簡設計模式》第01章:單例模式(Singleton)》,作者:冰 河。
單例設計模式
看幾個單例對象的示例代碼,其中有些代碼是線程安全的,有些則不是線程安全的,需要大家細細品味,這些代碼也是在高併發環境下測試驗證過的。
- 代碼一:SingletonExample1
這個類是懶漢模式,並且是線程不安全的
package io.binghe.concurrency.example.singleton; /** * @author binghe * @version 1.0.0 * @description 懶漢模式,單例實例在第一次使用的時候進行創建,這個類是線程不安全的 */ public class SingletonExample1 { private SingletonExample1(){} private static SingletonExample1 instance = null; public static SingletonExample1 getInstance(){ //多個線程同時調用,可能會創建多個對象 if (instance == null){ instance = new SingletonExample1(); } return instance; } }
- 代碼二:SingletonExample2
餓漢模式,單例實例在類裝載的時候進行創建,是線程安全的
package io.binghe.concurrency.example.singleton; /** * @author binghe * @version 1.0.0 * @description 餓漢模式,單例實例在類裝載的時候進行創建,是線程安全的 */ public class SingletonExample2 { private SingletonExample2(){} private static SingletonExample2 instance = new SingletonExample2(); public static SingletonExample2 getInstance(){ return instance; } }
- 代碼三:SingletonExample3
懶漢模式,單例實例在第一次使用的時候進行創建,這個類是線程安全的,但是這個寫法不推薦
package io.binghe.concurrency.example.singleton; /** * @author binghe * @version 1.0.0 * @description 懶漢模式,單例實例在第一次使用的時候進行創建,這個類是線程安全的,但是這個寫法不推薦 */ public class SingletonExample3 { private SingletonExample3(){} private static SingletonExample3 instance = null; public static synchronized SingletonExample3 getInstance(){ if (instance == null){ instance = new SingletonExample3(); } return instance; } }
- 代碼四:SingletonExample4
懶漢模式(雙重鎖同步鎖單例模式),單例實例在第一次使用的時候進行創建,但是,這個類不是線程安全的!!!!!
package io.binghe.concurrency.example.singleton; /** * @author binghe * @version 1.0.0 * @description 懶漢模式(雙重鎖同步鎖單例模式) * 單例實例在第一次使用的時候進行創建,這個類不是線程安全的 */ public class SingletonExample4 { private SingletonExample4(){} private static SingletonExample4 instance = null; //線程不安全 //當執行instance = new SingletonExample4();這行代碼時,CPU會執行如下指令: //1.memory = allocate() 分配對象的記憶體空間 //2.ctorInstance() 初始化對象 //3.instance = memory 設置instance指向剛分配的記憶體 //單純執行以上三步沒啥問題,但是在多線程情況下,可能會發生指令重排序。 // 指令重排序對單線程沒有影響,單線程下CPU可以按照順序執行以上三個步驟,但是在多線程下,如果發生了指令重排序,則會打亂上面的三個步驟。 //如果發生了JVM和CPU優化,發生重排序時,可能會按照下麵的順序執行: //1.memory = allocate() 分配對象的記憶體空間 //3.instance = memory 設置instance指向剛分配的記憶體 //2.ctorInstance() 初始化對象 //假設目前有兩個線程A和B同時執行getInstance()方法,A線程執行到instance = new SingletonExample4(); B線程剛執行到第一個 if (instance == null){處, //如果按照1.3.2的順序,假設線程A執行到3.instance = memory 設置instance指向剛分配的記憶體,此時,線程B判斷instance已經有值,就會直接return instance; //而實際上,線程A還未執行2.ctorInstance() 初始化對象,也就是說線程B拿到的instance對象還未進行初始化,這個未初始化的instance對象一旦被線程B使用,就會出現問題。 public static SingletonExample4 getInstance(){ if (instance == null){ synchronized (SingletonExample4.class){ if(instance == null){ instance = new SingletonExample4(); } } } return instance; } }
線程不安全分析如下:
當執行instance = new SingletonExample4();這行代碼時,CPU會執行如下指令:
1.memory = allocate() 分配對象的記憶體空間
2.ctorInstance() 初始化對象
3.instance = memory 設置instance指向剛分配的記憶體
單純執行以上三步沒啥問題,但是在多線程情況下,可能會發生指令重排序。
指令重排序對單線程沒有影響,單線程下CPU可以按照順序執行以上三個步驟,但是在多線程下,如果發生了指令重排序,則會打亂上面的三個步驟。
如果發生了JVM和CPU優化,發生重排序時,可能會按照下麵的順序執行:
1.memory = allocate() 分配對象的記憶體空間
3.instance = memory 設置instance指向剛分配的記憶體
2.ctorInstance() 初始化對象
假設目前有兩個線程A和B同時執行getInstance()方法,A線程執行到instance = new SingletonExample4(); B線程剛執行到第一個 if (instance == null){處,如果按照1.3.2的順序,假設線程A執行到3.instance = memory 設置instance指向剛分配的記憶體,此時,線程B判斷instance已經有值,就會直接return instance;而實際上,線程A還未執行2.ctorInstance() 初始化對象,也就是說線程B拿到的instance對象還未進行初始化,這個未初始化的instance對象一旦被線程B使用,就會出現問題。
- 代碼五:SingletonExample5
懶漢模式(雙重鎖同步鎖單例模式)單例實例在第一次使用的時候進行創建,這個類是線程安全的,使用的是 volatile + 雙重檢測機制來禁止指令重排達到線程安全
package io.binghe.concurrency.example.singleton; /** * @author binghe * @version 1.0.0 * @description 懶漢模式(雙重鎖同步鎖單例模式) * 單例實例在第一次使用的時候進行創建,這個類是線程安全的 */ public class SingletonExample5 { private SingletonExample5(){} //單例對象 volatile + 雙重檢測機制來禁止指令重排 private volatile static SingletonExample5 instance = null; public static SingletonExample5 getInstance(){ if (instance == null){ synchronized (SingletonExample5.class){ if(instance == null){ instance = new SingletonExample5(); } } } return instance; } }
- 代碼六:SingletonExample6
餓漢模式,單例實例在類裝載的時候(使用靜態代碼塊)進行創建,是線程安全的
package io.binghe.concurrency.example.singleton; /** * @author binghe * @version 1.0.0 * @description 餓漢模式,單例實例在類裝載的時候進行創建,是線程安全的 */ public class SingletonExample6 { private SingletonExample6(){} private static SingletonExample6 instance = null; static { instance = new SingletonExample6(); } public static SingletonExample6 getInstance(){ return instance; } }
- 代碼七:SingletonExample7
枚舉方式進行實例化,是線程安全的,此種方式也是線程最安全的
package io.binghe.concurrency.example.singleton; /** * @author binghe * @version 1.0.0 * @description 枚舉方式進行實例化,是線程安全的,此種方式也是線程最安全的 */ public class SingletonExample7 { private SingletonExample7(){} public static SingletonExample7 getInstance(){ return Singleton.INSTANCE.getInstance(); } private enum Singleton{ INSTANCE; private SingletonExample7 singleton; //JVM保證這個方法絕對只調用一次 Singleton(){ singleton = new SingletonExample7(); } public SingletonExample7 getInstance(){ return singleton; } } }