定義:
單例模式(singleton),保證一個類僅有一個實例,並且提供一個訪問它的全局訪問點。
這句話很好理解,今天我們的重點也不在於如何解讀單例模式。
在面試的過程中,往往會遇到考察手寫單例模式的場景,今天讓我們關注一下,寫單例模式的幾種方法。
餓漢式:
/** * 餓漢式. * * @author jialin.li * @date 2019-12-30 22:13 */ public class Singleton { private Singleton() { } private static Singleton singleton = new Singleton(); public Singleton getInstance(){ return singleton; } }
-
- 餓漢式的特點是類初始化的時候,創建了該對象。
- 由於類只會初始化一次,所以保證了對象只會被創建一次。
- 同時將構造方法私有化,保證了沒有辦法從外部創建對象。
這種方法的問題是,如果該實例從始至終都沒有被使用過,就會造成內存的浪費。
懶漢式:
/** * 懶漢式. * * @author jialin.li * @date 2019-12-30 22:13 */ public class Singleton { private Singleton() { } private volatile Singleton singleton = null; public Singleton getInstance(){ // 提高性能,降低線程進入臨界區的可能 if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
這種寫法又被成為
雙檢鎖模式,是一種實現單例模式的經典寫法。
代碼中有兩處判空:
-
- 第一處判空,是為了提高性能,降低線程進入臨界區的可能性。
- 第二處判空是為了線程同步,假如沒有第二處判空,則可能兩個線程都通過了if(singleton==null)條件,這樣即使是臨界區內只有一個線程在執行,臨界區內的代碼也會被執行兩遍,這樣就會產生兩個對象,不符合單例模式。
成員變量使用了volatile進行修飾,一方面是保證了對象在多線程環境下的可見性,另一方面是為了防止new Singleton()進行指令重排序而導致的並發問題。
volatile關鍵字的作用兩個:
-
- 保證變量在線程之間的可見性(直接從主存中讀寫數據,不經過工作內存)
- 阻止編譯時和運行時的指令重排,編譯時JVM編譯器遵循內存屏障約束,運行時依賴CPU屏障來阻止指令重排。
指令重排是指JVM在編譯Java代碼的時候,或者CPU在執行JVM字節碼的時候,對現有的指令順序進行重新排序。
指令重排的目的是為了在不改變程序執行結果的前提下,優化程序的運行效率。需要注意的是,這里所說的不改變執行結果,指的是不改變單線程下的程序執行結果。
這里不太好懂,舉一個例子,正常的new Singleton()創建步驟是:
- 開辟一塊內存空間
- 創建對象
- 將對象的地址存入引用變量
經過指令重排后,可能變成了:
- 開辟一塊內存空間
- 將對象的地址存入引用變量
- 創建對象
假設發生了指令重排,線程A、B都執行這段代碼,線程A執行到了new Singleton()的步驟2,此時還沒有創建對象,這個時候發生了線程的切換。線程B開始執行,這個時候線程B還可以通過if(singleton == null)的判斷,因為線程A中的singleton只是指向了一個空的內存地址,這個時候線程B創建出了一個Singleton對象,當線程切換成A時,線程A仍執行了new Singleton()的步驟3,此時創建了2個Singleton對象,不符合單例模式。
靜態內部類單例模式:
/** * 靜態內部類單例模式. * * @author jialin.li * @date 2019-12-30 22:13 */ public class Singleton { private Singleton() { } public static Singleton getInstance() { return Inner.singleton; } private static class Inner { private static Singleton singleton = new Singleton(); } }
這里利用的是內部類的特性,只有第一次調用getInstance方法的時候,虛擬機才會加載Inner並初始化singleton,並且只初始化一次。這種方法也是一種懶漢式的寫法,只有在需要的時候,才創建對象。
枚舉單例模式
枚舉類也是一種單例模式
public enum Singleton { INSTANCE //doSomething 該實例支持的行為 //可以省略此方法,通過Singleton.INSTANCE進行操作 public static Singleton get Instance() { return Singleton.INSTANCE; } }
這種寫法較為簡單,並且沒有辦法用反射的方式,創建對象。但是可讀性較差。
期待您的關注、推薦、收藏,同時也期待您的糾錯和批評。
