單例模式
要點:
- 一是某個類只能有一個實例
- 構造器私有化
- 二是它必須自行創建這個實例
- 含有一個該類的靜態變量來保存這個唯一的實例
- 三是它必須自行向整個系統提供這個實例
- 對外提供獲取該實例對象的方法
- (1)直接暴露
- (2)用靜態變量的get方法獲取
幾種常見的形式
-
餓漢式:直接創建對象,不存在線程安全的問題
-
直接實例化餓漢式(簡潔直觀)
package singletondemo; /** * 餓漢式: * 在類初始化時直接創建實例對象,不管是否需要這個對象都會創建 * (1)構造器私有化 * (2)自行創建,並且用靜態變量保存 * (3)向外提供這個實例 * (4)強調這是一個單例,我們可以用final修改 */ public class Singleton1 { public static final Singleton1 INSTANCE = new Singleton1(); private Singleton1() { } } // 備註:可以直接通過類名.來訪問
-
枚舉式(最簡潔)
package singletondemo; /** * 枚舉類型:表示該類型的對象是有限的幾個 * 我們可以限定爲一個,就成了單例 */ public enum Singleton2 { INSTANCE } // 備註:可以通過類名.來訪問
-
靜態代碼塊餓漢式(適合複雜實例化)
package singletondemo; public class Singleton3 { public static final Singleton3 INSTANCE; static{ INSTANCE = new Singleton3(); } private Singleton3() { } } // 備註:這個跟Singletion1是等效的,而且更復雜,試用於複雜的實例化
package singletondemo; import java.io.IOException; import java.util.Properties; public class Singleton3 { public static final Singleton3 INSTANCE; private String info; static{ try { Properties pro = new Properties(); // 假設在src下有一個singleton.properties配置文件 // 文件中有個info屬性 pro.load(Singleton3.class.getClassLoader(). getResourceAsStream("singleton.properties")); INSTANCE = new Singleton3(pro.getProperty("info")); } catch(IOException e) { throw new RuntimeException(e); } } private Singleton3(String info) { this.info = info; } // 添加get set方法,toString()方法進行測試驗證 } // 直接通過類名.來訪問,輸出其get方法可得到配置文件中info的屬性值
-
-
懶漢式:延遲創建對象
-
線程不安全(適用於單線程)
package singletondemo; /** * 懶漢式: * 延遲創建這個實例對象 * (1)構造器私有化 * (2)用一個靜態變量保存這個唯一的實例 * (3)提供一個靜態方法,獲取這個實例對象 * */ public class Singleton4 { private static Singleton4 instance; private Singleton4() { } public static Singleton4 getInstance() { if(instance == null) { // Thread.sleep(100) instance = new Singleton4(); } return instance; } } /* * 這段代碼是有可能發生線程安全問題的 * 當第一個線程進來,不是null,到了休眠時間 * 此時第二個線程進來,不是null,休眠 * 線程一結束休眠,new 了一個對象 * 線程二結束休眠,new 了一個對象 * 所以,此時,兩個對象是不一樣的 */
測試
package test; import singletondemo.Singleton4; public class Singleton4Test { public static void main(String[] args) { Singleton4 s1 = Singleton4.getInstance(); Singleton4 s2 = Singleton4.getInstance(); System.out.println(s1 == s2); System.out.println(s1); System.out.println(s2); } } // 輸出結果如下: // true // singletondemo.Singleton4@15db9742 // singletondemo.Singleton4@15db9742
-
線程安全(適用於多線程)
package singletondemo; /** * 懶漢式: * 延遲創建這個實例對象 * (1)構造器私有化 * (2)用一個靜態變量保存這個唯一的實例 * (3)提供一個靜態方法,獲取這個實例對象 * */ public class Singleton5 { private static Singleton5 instance; private Singleton5() { } public static Singleton5 getInstance() { if(instance == null) { synchronized (Singleton5.class) { if(instance == null) { // 休眠測試Thread.sleep(200) instance = new Singleton5(); } } } return instance; } }
-
靜態內部類形式(適用於多線程)
package singletondemo; /** * 在內部類被加載和初始化時,才創建INSTANCE實例對象 * 靜態內部類不會自動隨着外部類的加載和初始化而初始化,它是要單獨去加載和初始化的 * 因爲是在 內部類加載和初始化時創建的,因此是線程安全的 */ public class Singleton6 { private Singleton6() { } private static class Inner{ private static final Singleton6 INSTANCE = new Singleton6(); } public static Singleton6 getInstance() { return Inner.INSTANCE; } }
-
總結
- 如果是餓漢式,枚舉形式最簡單
- 如果是懶漢式,靜態內部類形式最簡單
- 注意線程安全問題