從單例模式說起
單例模式是我們比較常用的設計模式,玩好單例模式也會涉及到很多java基礎知識。
單例作為全局性實例,在多線程情況下全局共享的變量會變得非常危險。
雙重檢測:
雙重檢測是比較常用的一種實現方式:
public class Singleton { public static final volatile Singleton singleton = null; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronize (Singleton.class){ if( singleton == null ) { singleton = new Singleton(); } } return singleton; } }
如果不用volatile修飾,多線程執行到 singleton == null 時,多個實例會被創建出來,就可能造成內存泄露問題。
當然你可以說可以用互斥同步的方式進行,但是我們做了同步,多線程的操作就變成了串型了,效率會很低,因為創建對象其實只需要一次,但是後面的讀取都需要同步了。
還有一個原因,在jvm編譯器可能會對指令進行重拍和優化,就是判斷singleton == null的判斷順序可能無法保證。
於是我們將變量用volatile修飾,這個變量就不會在多線程中存在副本,都必須從主內存讀取,同時避免了指令重拍。
當兩個線程執行完第一個 singleton == null 後等待鎖, 其中一個線程獲得鎖並進入synchronize後,實例化了,然後退出釋放鎖,另外一個線程獲得鎖,進入又想實例化,會判斷是否進行實例化了,如果存在,就不進行實例化了。
靜態內部類(懶漢模式)
一個延遲實例化的內部類的單例模式,一個內部類的容器,調用getInstance時,JVM加載這個類
public final class Singleton { private static class SingletonHolder { static final Singleton INSTANCE = new Singleton(); } private Singleton() {} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
由於SingleHolder是私有的,除了getInstance()之外沒有方法可以訪問它,只有在getInstance()被調用時才會真正創建,
首先,其他類在引用這個Singleton的類時,只是新建了一個引用,並沒有開辟一個的堆空間存放(對象所在的內存空間)。
接著,當使用Singleton.getInstance()方法後,Java虛擬機(JVM)會加載SingletonHolder.class(JLS規定每個class對象只能被初始化一次),並實例化一個Singleton對象。
缺點:
需要在Java的另外一個內存空間(Java PermGen 永久代內存,這塊內存是虛擬機加載class文件存放的位置)占用一個大塊的空間。
從單例模式說起