設計模式系列之單例模式

本文按部就班介紹單例模式的幾種實現方式,以及Jdk中使用到單例模式的例子,以及sring框架中使用到的單例模式例子。java

餓漢式

package signgleton;

/**
 * 單例模式簡單的實現
 */
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {

    }
    public static Singleton getInstance() {
        return instance;
    }

}

」餓漢式「只是形象的比喻,由於他想要這個實例的時候,不須要等待,別廢話,給哥拿來。經過static的初始化方式,藉助類第一次被加載時,就把Singleton實例給建立出來了,並存儲在JVM的方法區,屬於類變量,被全部的實例共享。不一樣的線程調用都返回一個實例,因此這樣也保證了線程安全。spring

它還有個孿生兄弟,靜態代碼塊來實例化:編程

package signgleton;

/**
 * 經過靜態代碼塊建立實例對象
 */
public class StaticSignleton {

    private static StaticSignleton instance;

    /**
     * 靜態代碼塊建立實例
     */
    static {
       instance = new StaticSignleton();
    }
    
    private StaticSignleton() {

    }
    public static StaticSignleton getInstance() {
        return instance;
    }
}

科普一下類初始化順序:緩存

  • 靜態變量、靜態代碼塊初始化
  • 構造函數
  • 自定義構造函數

餓漢式缺點:由於在類被加載的時候對象就會被實例化,這可能會形成沒必要要的消耗,若是你的程序不在意這點消耗那就當我沒說。安全

下面介紹兩種方式解決上面的問題:第一是使用靜態內部類,第二是使用懶漢式多線程

靜態內部類

package signgleton;

/**
 * 使用靜態內部類獲取單例實例
 */
public class StaticInnerClassSingleton {
    
    private static class InnerSingletonClass{
        private static final StaticInnerClassSingleton innerInstance = new StaticInnerClassSingleton();
        
    }
    private StaticInnerClassSingleton() {
        
    }
    public static final StaticInnerClassSingleton getStaticInstance() {
        return InnerSingletonClass.innerInstance;
    }
}

靜態內部類一樣藉助了JVM這個大佬來保證線程安全,只是他在類加載的時候並無當即實例化對象,而是採用了延遲加載策略,只有調用getStaticInstance的時候才用內部類去建立實例框架

線程不安全的懶漢式

package signgleton;

/**
 * 線程不安全的懶漢式
 */
public class UnsafeSingleton {
    
    private static UnsafeSingleton unsafeSingleton;
    private UnsafeSingleton() {
        
    }
    public static UnsafeSingleton getUnsafeSingleton() {
        if (unsafeSingleton == null) {
            unsafeSingleton = new UnsafeSingleton();
        }
        return unsafeSingleton;
    }
    
}

雖然這樣寫達到了使用的時候才實例化的目的,可是也帶來的線程安全問題。在多線程下,可能有兩個以上的線程同時進入if(unsafeInstance == null),這樣會發生一些奇怪不定的結果。ide

線程安全的懶漢式

package signgleton;

/**
 * 線程安全的懶漢式
 */
public class SafeSingleton {
    private static SafeSingleton safeSingleton;
    private SafeSingleton() {

    }
    public static synchronized SafeSingleton getSafeSingleton() {
        if (safeSingleton == null) {
            safeSingleton = new SafeSingleton();
        }
        return safeSingleton;
    }
}

這種方式在方法上加synchronized同步關鍵字解決了餓漢式線程安全問題,可是由於每次調用都加鎖,極大地下降了性能,由於只有第一次建立實例時須要加鎖,弄成如今每次都加鎖。有沒有解決辦法呢,固然有,前輩們都是很聰明的,想出了雙重校驗鎖這個經典的例子.函數

雙重校驗鎖

package signgleton;

/**
 * 線程不安全雙重校驗鎖
 */
public class UnSafeTwoCheckSingleton {
    private static UnSafeTwoCheckSingleton singleton;
    private UnSafeTwoCheckSingleton() {
        
    }
    public static UnSafeTwoCheckSingleton getSingleton() {
        if (singleton == null) {
            synchronized (UnSafeTwoCheckSingleton.class) {
                if (singleton == null) {
                    singleton = new UnSafeTwoCheckSingleton();
                }
            }
        }
        return singleton;
    }
}

雙重校驗鎖的形式主要是縮小了鎖的範圍,可是熟悉多線程編程的同窗就能夠看得出來,即便這樣作仍是有線程安全問題,這裏存在一個多個線程共享變量的可見性問題(這部分我不太懂原理),解決方案就是使用volatile性能

使用volatile優化

package signgleton;

/**
 * 線程安全雙重校驗鎖
 */
public class SafeTwoCheckSingleton {
    private static volatile SafeTwoCheckSingleton singleton;
    private SafeTwoCheckSingleton() {
        
    }
    public static SafeTwoCheckSingleton getSingleton() {
        if (singleton == null) {
            synchronized (SafeTwoCheckSingleton.class) {
                if (singleton == null) {
                    singleton = new SafeTwoCheckSingleton();
                }
            }
        }
        return singleton;
    }
}

你覺得這樣就安全了嗎,就想下班了嗎?還沒完,序列化這個惡棍會破壞單例,防範序列化這個惡棍破壞單例,能夠在類中定義咱們獲取實例的策略,既加readResolve。

防範序列化破壞單例

package signgleton;

import java.io.Serializable;

/**
 * 線程安全雙重校驗鎖
 */
public class SafeTwoCheckSingleton implements Serializable{
    private static volatile SafeTwoCheckSingleton singleton;
    private SafeTwoCheckSingleton() {

    }
    public static SafeTwoCheckSingleton getSingleton() {
        if (singleton == null) {
            synchronized (SafeTwoCheckSingleton.class) {
                if (singleton == null) {
                    singleton = new SafeTwoCheckSingleton();
                }
            }
        }
        return singleton;
    }
    
    private Object readResolve() {
        return singleton;
    }
}

單例模式案例

這麼多的實現方式,你會問,有什麼用?用處可大了,下面講兩個使用實例,一個jdk的Runtime, 一個是Spring框架中的單例模式。

Runtime:是一個封裝了JVM進程的類,每個JAVA程序實際上都是JVM的一個進程,每個進程都是對應這麼一個Runtime實例。
源碼以下:

public class Runtime {
        private static Runtime currentRuntime = new Runtime();


        public static Runtime getRuntime() {
            return currentRuntime;
        }
        private Runtime() {}
    }

這裏使用了餓漢式單例模式。

下面咱們來看看看spring 中的單例模式,spring中使用的是單例註冊表的特殊方式實現的單例模式,因此說模式是死的,須要靈活得運用。

看看單例註冊表的實現原理demo:

package signgleton;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 單例註冊表demo
 */
public class SingletonRegTest {

    private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<String,Object>();

    /**
     * 類加載時初始化一個實例
     */
    static {
        SingletonRegTest singletonRegTest = new SingletonRegTest();
        singletonObjects.put(singletonRegTest.getClass().getName(), singletonRegTest);
    }

    public static SingletonRegTest getInstance(String name) {
        if (name == null) {
            // 默認分配一個實例
            name = "signgleton.SingletonRegTest";
        }
        if (singletonObjects.get(name) == null) {
            try {
                // 將默認實例放入緩存中
                singletonObjects.put(name, Class.forName(name).newInstance());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return (SingletonRegTest) singletonObjects.get(name);
    }
}

再來看看spring 源碼:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

    @SuppressWarnings("unchecked")
    protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {
        final String beanName = transformedBeanName(name);
        Object bean;
        // 從單例註冊表中檢查是否存在單例緩存
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            ...
            // 返回緩存實例
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
        else {
            ...
            try {
                 ...

                // 若是是單例模式
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                        @Override
                        public Object getObject() throws BeansException {
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            catch (BeansException ex) {
                                ...
                            }
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                // 若是是原型模式
                else if (mbd.isPrototype()) {
                    ...
                }
                // 其餘模式
                else {
                    ...
                }
            }
            catch (BeansException ex) {
               ...
            }
        }
        return (T) bean;
    }
}

咱們進入 getSingleton()方法:

import java.util.Map;

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

    // 經過 ConcurrentHashMap 實現單例註冊表
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            // 檢查緩存中是否存在實例  
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                ...
                try {
                    singletonObject = singletonFactory.getObject();
                }
                catch (BeanCreationException ex) {
                    ...
                }
                finally {
                   ...
                }
                // 若是實例對象在不存在,咱們註冊到單例註冊表中。
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }

    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

        }
    }
}

是否是和咱們的單例註冊表demo很類似。單例模式的講解後面隨着學習到其餘框架再作相應的補充,也歡迎你們獻言獻策。

相關文章
相關標籤/搜索