單例模式是 Java 比較簡單,也是最基礎,最經常使用的設計模式之一。在運行期間,保證某個類只建立一個實例,保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。設計模式
單例模式主要有餓漢式單例、懶漢式單例、靜態內部類實現單例、枚舉類實現單例等,不一樣的方式有不一樣的優缺點,下面介紹各個實現方式和優缺點。安全
建立餓漢式單例簡單粗暴,在類被虛擬機加載時就建立類對象;併發
缺點:可能在還不須要此實例的時候就已經把實例建立出來了,沒起到lazy loading的效果;jvm
優勢:實現簡單,並且安全可靠;函數
package pattern.single; /** * 餓漢式單例模式 不存在線程安全問題,由於在類加載時就建立好了對象 * @author ningbeibei */ public class HungrySingle { // 當類加載時建立對象 private static HungrySingle hungery = new HungrySingle(); // 私有化構造函數,屏蔽外部建立對象 private HungrySingle() { } // 提供全局訪問點,直接返回實例對象 public static HungrySingle getHungrySingle() { return hungery; } }
測試類代碼測試
package pattern.single; /** * 餓漢式單例模式測試類 * @author ningbeibei */ public class test { public static void main(String[] args) { //餓漢式單例模式,獲取 HungrySingle 實例 HungrySingle hungry = HungrySingle.getHungrySingle(); System.out.println("HungrySingle對象:"+hungry); } }
運行結果spa
相比餓漢式,懶漢式實現了用則建立不用則不建立,真正實現了懶加載效果;線程
package pattern.single; /** * 懶漢式單例模式 * @author ningbeibei */ public class SinglePattern { //聲明對象變量 private static SinglePattern single = null; //私有化構造函數 private SinglePattern(){ } //對外提供獲取對象得方法 public static SinglePattern getSinglePattern() { if(single==null){ return new SinglePattern(); } return single; } }
注意:上面代碼實現了懶漢式單例,getSinglePattern()方法先判斷實例是否爲空再決定是否去建立實例,看起來彷佛很完美,可是存在線程安全問題。在併發獲取實例的時候,可能會存在構建了多個實例的狀況。因此,須要對此代碼進行下改進,確保實例惟一。設計
改進後代碼code
package pattern.single; /** * 雙重檢查加鎖 * 線程安全懶漢模式 * @author ningbeibei */ public class LockSinglePattern { //聲明變量, 使用volatile關鍵字確保絕對線程安全 private volatile static LockSinglePattern lockSingle =null; //私有化構造函數 private LockSinglePattern() { } //提供全局惟一獲取實例方法 public static LockSinglePattern getLockSinglePattern() { //判斷實例是否null if(lockSingle==null) { //對單例類進行加鎖 synchronized (LockSinglePattern.class) { //在判斷是否爲null if(lockSingle==null) { //建立實例 lockSingle = new LockSinglePattern(); } } } //返回實例 return lockSingle; } }
注意:這裏採用了雙重校驗的方式,對懶漢式單例模式作了線程安全處理。經過加鎖,能夠保證同時只有一個線程走到第二個判空代碼中去,這樣保證了只建立 一個實例。這裏還用到了volatile關鍵字來修飾lockSingle,其最關鍵的做用是防止指令重排。
測試類
package pattern.single; /** * 懶漢式單例模式測試類 * @author ningbeibei */ public class test { public static void main(String[] args) { //雙重加鎖,線程安全懶漢式單例模式 LockSinglePattern lock= LockSinglePattern.getLockSinglePattern(); System.out.println("線程安全懶漢式單例模式:"+lock); } }
運行結果
經過靜態內部類的方式實現單例模式是線程安全的,由於內部靜態類只會被加載一次,故該實現方式是線程安全的
代碼以下:
package pattern.single; /** * 靜態內部類實現單例模式 * @author ningbeibei */ public class InteriorSingle { /** * 靜態內部類 * @author ningbeibei */ private static class Insingle { //靜態初始化器,由jvm來報證線程安全 private static InteriorSingle single = new InteriorSingle(); } //私有化構造函數 private InteriorSingle() { } //提供全局惟一訪問點 public static InteriorSingle getSingle() { return Insingle.single; } }
注意:經過靜態內部類的方式實現單例模式是線程安全的,同時靜態內部類不會在InteriorSingle類加載時就加載,而是在調用getSingle()方法時才進行加載,達到了懶加載的效果。彷佛靜態內部類看起來已是最完美的方法了,其實不是,可能還存在反射攻擊或者反序列化攻擊。
測試類
package pattern.single; /** * 懶漢式單例模式測試類 * @author ningbeibei */ public class test { public static void main(String[] args) throws Exception { //靜態內部類實現單例模式。這個也式線程安全得 InteriorSingle Interior = InteriorSingle.getSingle(); System.out.println("靜態內部類單例模式:"+Interior); } }
運行結果
最佳的單例實現模式就是枚舉模式。利用枚舉的特性,讓JVM來幫咱們保證線程安全和單一實例的問題。並且寫法還特別簡單。
代碼以下
package pattern.single; /** * 枚舉類 * @author ningbeibei */ public enum TypeSingle { // 定義一個枚舉的元素,它 就表明了Singleton的一個實例 TYPESINGLE; //業務方法 public void get() { System.out.println("枚舉中的方法"); } }
測試代碼
package pattern.single; /** * 懶漢式單例模式測試類 * @author ningbeibei */ public class test { public static void main(String[] args) throws Exception { //枚舉實現單例模式 TypeSingle.TYPESINGLE.get(); } }
運行結果
反射破壞單例;
序列化破壞單例;
這兩中破壞單例模式的方式不會對枚舉單例方式構成威脅,因此通常都推薦枚舉實現單例模式;
以上列舉了多種單例模式的寫法,在不一樣的場景中有不一樣應用;
寫的不足之處還望指正,以便我及時更正避免讀者誤解;