單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於建立型模式,它提供了一種建立對象的最佳方式。
這種模式涉及到一個單一的類,該類負責建立本身的對象,同時確保只有單個對象被建立。這個類提供了一種訪問其惟一的對象的方式,能夠直接訪問,不須要實例化該類的對象。
注意:git
顧名思義,餓漢式單例它很「餓」,因此一開始就建立了惟一但單例實例,但若是你沒有使用過這個實例,就會形成內存的浪費github
/** * 餓漢式單例 * 優勢:簡單,在類裝載時就完成了實例化,避免了線程同步問題,線程安全 * 缺點:因爲這個類已經完成了實例化,若是從始至終都沒有用過這個實例,就會形成內存的浪費 */ public class SingletonTest01 { public static void main(String[] args) { Signleton instance1= Signleton.getInstance(); Signleton instance2 = Signleton.getInstance(); System.out.println(instance1==instance2); System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); } } class Signleton{ //一、構造器私有化,外部沒法經過new新建 private Signleton(){ } //二、內部建立對象實例 private final static Signleton instance = new Signleton(); //三、提供一個公有的靜態方法,返回實例對象 public final static Signleton getInstance(){ return instance; } }
輸出結果設計模式
true 1163157884 1163157884
能夠看到輸出的是同一個實例安全
和以前的方式相似,只不過將類實例化的過程放在了靜態代碼塊中,也就是類裝載的時候,bash
就執行靜態代碼塊中的代碼,優缺點和以前同樣多線程
/** * 和以前的方式相似,只不過將類實例化的過程放在了靜態代碼塊中,也就是類裝載的時候, * 就執行靜態代碼塊中的代碼,優缺點和以前同樣 */ public class SingletonTest02 extends Thread{ public static void main(String[] args) { Signleton instance1= Signleton.getInstance(); Signleton instance2 = Signleton.getInstance(); System.out.println(instance1==instance2); System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); } } class Signleton{ //一、構造器私有化,外部沒法經過new新建 private Signleton(){} //二、內部建立對象實例 private static Signleton instance; static {//靜態代碼塊種,建立單例對象 instance = new Signleton(); } //三、提供一個公有的靜態方法,返回實例對象 public final static Signleton getInstance(){ return instance; } }
輸出線程
true 1163157884 1163157884
一樣,顧名思義,懶漢式單例它很懶。只有在你用到它時,它纔會建立一個實例。設計
/** * 餓漢式-線程不安全 * 優勢:起到了懶加載的效果,可是隻能在單線程下使用 * 若是在多線程下,若是一個線程進入了if判斷語句塊, * 還沒來得及向下執行,另外一個線程也進入這個判斷語句,就會產生多個實例(違背單例模式), * 實際開發中,不要使用這種方式 */ public class SingletonTest03 { public static void main(String[] args) { for (int i = 0; i <10 ; i++) { new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start(); } } } class Signleton{ private static Signleton instance; private Signleton(){} //提供一個靜態的公有方法,當調用方法時,纔去建立instance public static Signleton getInstance(){ if(instance == null){//若是爲空再去建立對象 instance = new Signleton(); } return instance; } }
輸出code
546405844 135417039 135417039 802181073 135417039 135417039 135417039 802181073 135417039 135417039
這裏我選了個比較極端的狀況,若是你的電腦配置比較好,可能運行幾回結果都是符合單例模式的。
上面方法之因此會存在線程不安全的狀況,是由於多線程狀況下,可能會有多條線程同時判斷單例是否建立。那麼要解決這個問題 ,只須要同步getInstance()方法
/** * 解決了線程不安全的問題 * 可是大大下降了效率 每一個線程想得到實例的時候,執行getInstance()方法都要進行同步 */ public class SingletonTest04 { public static void main(String[] args) { for (int i = 0; i <10 ; i++) { new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start(); } } } class Signleton{ private static Signleton instance; private Signleton(){} //提供一個靜態的公有方法,當調用方法時,纔去建立instance public static synchronized Signleton getInstance(){ if(instance == null){//若是爲空再去建立對象 instance = new Signleton(); } return instance; } }
結果
802181073 802181073 802181073 802181073 802181073 802181073 802181073 802181073 802181073 802181073
可是,synchronized是一個很重量的同步鎖,而咱們每次執行getInstance()時都會進行同步,極其影響效率
雙檢鎖,又叫雙重校驗鎖,綜合了懶漢式和餓漢式二者的優缺點整合而成。看上面代碼實現中,特色是在synchronized關鍵字內外都加了一層 if 條件判斷,這樣既保證了線程安全,又比直接上鎖提升了執行效率,還節省了內存空間
/** * 懶漢模式-雙重檢查 * 進行了兩次if判斷檢查,這樣就保證線程安全了 * 經過判斷是否爲空,來肯定是否 須要再次實例化 */ public class SingletonTest05 { public static void main(String[] args) { for (int i = 0; i <10 ; i++) { new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start(); } } } class Signleton{ private static volatile Signleton instance;//volatile保證可見性 private Signleton(){} //提供一個靜態的公有方法,加入雙重檢查代碼,解決線程安全問題,同時解決懶加載問題 public static Signleton getInstance() { if (instance == null) { synchronized (Signleton.class) { if (instance == null) { instance = new Signleton(); } } } return instance; } }
運行結果
79372097 79372097 79372097 79372097 79372097 79372097 79372097 79372097 79372097 79372097
推薦使用
/** * 靜態內部類實現單例模式 * 該方法採用了類裝載機制來保證初始化實例時只有一個線程 * 靜態內部類在Signleton類被裝載時並不會當即實例化,而是須要實例化時,纔會裝載SignletonInstance類 * 類的靜態屬性只會在第一次加載類的時候初始化 * 避免了線程不安全,利用靜態內部類實現懶加載,效率高 */ public class SingletonTest07 { public static void main(String[] args) { for (int i = 0; i <10 ; i++) { new Thread(() -> System.out.println(Signleton.getInstance().hashCode()) ).start(); } } } class Signleton{ //構造器私有 private Signleton(){} //靜態內部類,該類中有一個靜態屬性Signleton private static class SignletonInstance{ private static final Signleton instance = new Signleton(); } //提供一個靜態的公有方法,直接返回SignletonInstance.instance public static Signleton getInstance() { return SignletonInstance.instance; } }
結果
79372097 79372097 79372097 79372097 79372097 79372097 79372097 79372097 79372097 79372097
這種方式較爲簡單,推薦使用
/** * @author codermy * @createTime 2020/5/14 * 枚舉方法實現單例模式 * 藉助jdk1.5中添加的枚舉類來實現單例模式, * 不只能避免多線程同步問題,並且還能防止反序列化從新建立新對象 */ public class SingletonTest08 { public static void main(String[] args) { Singleton singleton = Singleton.INSTANCE; singleton.Ok(); for (int i = 0; i <10 ; i++) { new Thread(() -> System.out.println(Singleton.INSTANCE.hashCode()) ).start(); } } } enum Singleton{ INSTANCE;//屬性 public void Ok(){ System.out.println("ok"); } }
結果
ok 858497792 858497792 858497792 858497792 858497792 858497792 858497792 858497792 858497792 858497792
能夠看出,枚舉實現單例模式,最爲簡潔,較爲推薦。可是正是由於它簡潔,致使可讀性較差