一、單例模式 1、什麼是單例模式 採取一定的方法,使程式中的某個類只存在一個實例對象,且該類對外提供一個獲取該對象的方法(一般為靜態方法)。 2、單例模式分類 (1)餓漢式(2種寫法,線程安全) 靜態變數 靜態代碼塊 (2)懶漢式(3種寫法) 線程不安全 線程安全,同步方法 線程安全,同步代碼塊(不 ...
一、單例模式
1、什麼是單例模式
採取一定的方法,使程式中的某個類只存在一個實例對象,且該類對外提供一個獲取該對象的方法(一般為靜態方法)。
2、單例模式分類
(1)餓漢式(2種寫法,線程安全)
靜態變數
靜態代碼塊
(2)懶漢式(3種寫法)
線程不安全
線程安全,同步方法
線程安全,同步代碼塊(不推薦使用)
(3)雙重檢查(推薦使用)
(4)靜態內部類(推薦使用)
(5)枚舉(推薦使用)
3、餓漢式單例模式(靜態常量版)
(1)步驟:
step1:構造器私有化(防止通過new創建實例對象)
step2:在類的內部創建實例對象。
step3:向外暴露一個靜態的公共方法用於獲取實例對象。
(2)代碼實現:
package singleton.pattern.demo1; /** * 演示 餓漢式單例模式,靜態變數版 * */ public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getSingleTon(); Singleton singleton2 = Singleton.getSingleTon(); System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 餓漢式單例模式,靜態變數版 */ class Singleton { // 在類的內部創建實例對象。使用靜態變數,只被載入一次。 private static Singleton singleton = new Singleton(); /** * 構造器私有化(防止通過new創建實例對象) */ private Singleton() { } /** * 向外暴露一個靜態的公共方法用於獲取實例對象。 * * @return 實例對象 */ public static Singleton getSingleTon() { return singleton; } }
(3)優缺點:
優點:寫法簡單,在類裝載時完成了實例化,避免線程同步問題。
缺點:在類裝載前完成實例化,沒有實現懶載入(Lazy Loading),可能造成記憶體的浪費(比如從不使用該類時會造成記憶體的浪費)。
(4)UML圖:
4、餓漢式單例模式(靜態代碼塊)
(1)步驟:
step1:構造器私有化(防止通過new創建實例對象)
step2:在類的內部聲明實例對象,併在靜態代碼塊中實例化對象。
step3:向外暴露一個靜態的公共方法用於獲取實例對象。
(2)代碼實現:
package singleton.pattern.demo2; /** * 演示 餓漢式單例模式,靜態代碼塊版 * */ public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getSingleTon(); Singleton singleton2 = Singleton.getSingleTon(); System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 餓漢式單例模式,靜態代碼塊版 */ class Singleton { // 在類的內部聲明一個實例對象 private static Singleton singleton; static { // 在代碼塊中實例化一個對象,同樣只載入一次 singleton = new Singleton(); } /** * 構造器私有化(防止通過new創建實例對象) */ private Singleton() { } /** * 向外暴露一個靜態的公共方法用於獲取實例對象。 * * @return 實例對象 */ public static Singleton getSingleTon() { return singleton; } }
(3)優缺點同上例 餓漢式單例模式(靜態常量版)
5、懶漢式單例模式(線程不安全)
(1)步驟:
step1:構造器私有化(防止通過new創建實例對象)
step2:在類的內部聲明實例對象。
step3:向外暴露一個靜態的公共方法用於獲取實例對象,在調用該方法時,才去創建實例對象。
(2)代碼實現:
package singleton.pattern.demo3; /** * 演示 懶漢式單例模式,線程不安全版 * */ public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getSingleTon(); Singleton singleton2 = Singleton.getSingleTon(); System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 懶漢式單例模式,線程不安全版 */ class Singleton { // 在類的內部聲明一個實例對象 private static Singleton singleton; /** * 構造器私有化(防止通過new創建實例對象) */ private Singleton() { } /** * 向外暴露一個靜態的公共方法用於獲取實例對象。 當調用該方法時,才去檢查並創建一個實例對象。 * * @return 實例對象 */ public static Singleton getSingleTon() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
(3)優缺點:
優點:實現了懶載入。
缺點:只能在單線程下使用,比如線程A與線程B併發執行到 if (singleton == null), 此時便會產生多個實例對象。
6、懶漢式單例模式(線程安全,同步方法)
(1)步驟:
step1:構造器私有化(防止通過new創建實例對象)
step2:在類的內部聲明實例對象。
step3:向外暴露一個靜態的公共方法(給靜態方法加個synchronized關鍵字,解決同步問題)用於獲取實例對象,在調用該方法時,才去創建實例對象。
(2)代碼實現:
package singleton.pattern.demo4; /** * 演示 懶漢式單例模式,線程安全,同步方法版 * */ public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getSingleTon(); Singleton singleton2 = Singleton.getSingleTon(); System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 懶漢式單例模式,線程安全,同步方法版 */ class Singleton { // 在類的內部聲明一個實例對象 private static Singleton singleton; /** * 構造器私有化(防止通過new創建實例對象) */ private Singleton() { } /** * 向外暴露一個靜態的公共方法用於獲取實例對象,並給方法加個synchronized關鍵字,解決同步的問題。 * 當調用該方法時,才去檢查並創建一個實例對象。 * * @return 實例對象 */ public static synchronized Singleton getSingleTon() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
(3)優缺點:
優點:實現了懶載入,解決了線程不安全的問題。
缺點:效率太低,每個線程想獲取實例對象時,均會觸發同步方法,此時會等待前一個線程調用結束後才能使用,使效率低。
7、懶漢式單例模式(同步代碼塊方法,線程不一定安全)
(1)步驟:
step1:構造器私有化(防止通過new創建實例對象)
step2:在類的內部聲明實例對象。
step3:向外暴露一個靜態的公共方法(在靜態方法內部定義一個代碼塊,解決同步問題)用於獲取實例對象,在調用該方法時,才去創建實例對象。
(2)代碼實現:
package singleton.pattern.demo5; /** * 演示 懶漢式單例模式,線程不一定安全,同步代碼塊版 * */ public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getSingleTon(); Singleton singleton2 = Singleton.getSingleTon(); System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 懶漢式單例模式,線程不一定安全,同步代碼塊版 */ class Singleton { // 在類的內部聲明一個實例對象 private static Singleton singleton; /** * 構造器私有化(防止通過new創建實例對象) */ private Singleton() { } /** * 向外暴露一個靜態的公共方法用於獲取實例對象,在方法內部加個同步代碼塊。 當調用該方法時,才去檢查並創建一個實例對象。 * * @return 實例對象 */ public static Singleton getSingleTon() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }
(3)優缺點:
優點:優化了了上例 懶漢式單例模式(線程安全,同步方法) 的效率問題。
缺點:能滿足大部分線程安全的情況,但是若線程A與線程B併發執行到 if (singleton == null),此時同步代碼塊的作用就不存在了, 會產生多個實例對象。
8、雙重檢查(Double Check)
(1)步驟:
step1:構造器私有化(防止通過new創建實例對象)
step2:在類的內部聲明實例對象,並使用volatile關鍵字(保證可見性,且防止因JVM指令重排使代碼執行順序不對,從而導致代碼執行有誤)。
step3:向外暴露一個靜態的公共方法(在靜態方法內部定義一個代碼塊,解決同步問題)用於獲取實例對象,在調用該方法時,才去創建實例對象。在上例 懶漢式單例模式(同步代碼塊方法,線程不一定安全) 的基礎上,給同步代碼塊內部加個檢查處理。
(2)代碼實現:
package singleton.pattern.demo6; /** * 演示 單例模式,雙重檢查版 * */ public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getSingleTon(); Singleton singleton2 = Singleton.getSingleTon(); System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 單例模式,雙重檢查版 */ class Singleton { // 在類的內部聲明一個實例對象 private static volatile Singleton singleton; /** * 構造器私有化(防止通過new創建實例對象) */ private Singleton() { } /** * 向外暴露一個靜態的公共方法用於獲取實例對象,在方法內部加個同步代碼塊,在代碼塊內部增加一個檢查處理。 當調用該方法時,才去檢查並創建一個實例對象。 * * @return 實例對象 */ public static Singleton getSingleTon() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
(3)優缺點:
優點:線程安全,延遲載入,效率高。常用於多線程開發。
9、靜態內部類
(1)步驟:
step1:構造器私有化(防止通過new創建實例對象)
step2:在類的內部定義一個靜態內部類(只有被調用時,才會被載入),併在內部類中實例化對象。
step3:向外暴露一個靜態的公共方法,併在方法中調用靜態內部類,用於獲取實例對象。
(2)代碼實現:
package singleton.pattern.demo7; /** * 演示 單例模式,靜態內部類版 * */ public class Demo { public static void main(String[] args) { Singleton singleton1 = Singleton.getSingleTon(); Singleton singleton2 = Singleton.getSingleTon(); System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 單例模式,靜態內部類版 */ class Singleton { /** * 構造器私有化(防止通過new創建實例對象) */ private Singleton() { } /** * 靜態內部類,在被調用的時候才會被載入,實現懶載入。 且內部使用靜態常量實例化一個對象,保證了線程安全問題。 */ public static class SingleTonInstance { public static final Singleton INSTANCE = new Singleton(); } /** * 向外暴露一個靜態的公共方法用於獲取實例對象,在方法內部加個同步代碼塊,在代碼塊內部增加一個檢查處理。 當調用該方法時,才去檢查並創建一個實例對象。 * * @return 實例對象 */ public static Singleton getSingleTon() { return SingleTonInstance.INSTANCE; // 調用靜態內部類的靜態屬性 } }
(3)優缺點:
優點:利用JVM的類載入機制,保證了實例化對象時只有一個線程,從而線程安全。在被調用時靜態內部類才會被載入並實例化對象,從而實現懶載入,效率高。
(4)UML圖:
10、枚舉
(1)步驟:
step1:定義一個枚舉類型。
step2:調用即可
(2)代碼實現:
package singleton.pattern; /** * 演示 單例模式,枚舉版 * */ public class Demo { public static void main(String[] args) { SingleTon singleton1 = SingleTon.INSTACNE; SingleTon singleton2 = SingleTon.INSTACNE; System.out.println(singleton1 == singleton2); // 由於獲取的為同一個對象,所以輸出為true } } /** * 單例模式,枚舉版 */ enum SingleTon { INSTACNE; public void show() { System.out.println("hello world"); } }
(3)優缺點:
優點:使用枚舉類型創建(enum本質是個繼承java.lang.Enum類的final class),保證線程安全,且可以防止反序列化重新創建新的對象。
11、JDK中的單例模式舉例(Runtime)
(1)部分源碼
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} }
(2)可以看到上述代碼中採用的是 餓漢式單例模式(靜態變數版)。
12、單例模式使用註意
(1)當頻繁創建、銷毀某個對象時,可以採用單例模式。
(2)使用單例模式時,需調用相關方法獲取實例,而不是通過new。
(3)當創建對象消耗資源過多時,但又經常使用時,可以採用單例模式創建。