1. 程式人生 > >單例模式那點事

單例模式那點事

一、單例模式

    1、餓漢式

/**
 * 餓漢式單例模式
 */
public class Singleton implements Serializable{
    //餓漢式 直接建立
    private static Singleton singleton = new Singleton();
    //構造器私有
    private Singleton(){
    }
    //提供一個靜態方法來獲取單例例項
    public static  Singleton getInstance(){
        return singleton;
    }
}

    2、懶漢式

/**
 * 懶漢式單例模式
 */
public class LazySingleton implements Serializable{
    //懶漢式 
    private static LazySingleton singleton = null;
    //構造器私有
    private LazySingleton(){
    }
    //提供一個靜態方法來獲取單例例項 先檢視例項是否為null 如果為null 先建立,有的話直接返回
    public static LazySingleton getInstance(){
        if(singleton == null){
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

二、單例模式改進

     1、缺點

       上面的兩種單例模式,在單執行緒模式下是可以完整的執行的,但是在多執行緒環境中,可以會出現多個例項的情況出現,這就違背了單例的規則

    2、雙重加鎖機制

public class Singleton implement Serializable{
    private static Singleton singleton = null;
    //構造器私有
    private ProjectPoint(){
    }

    //DCL 雙重校驗
    public static synchronized ProjectPoint getInstance(){
        if(singleton == null){
            synchronized(ProjectPoint.class){
                if (singleton == null){
                    singleton =  new ProjectPoint();
                }
            }
        }
        return  singleton;
    }
}

   在類中獲取例項使用了同步方法,和同步程式碼塊的形式,在多執行緒中可以很好的避免了單例模式下獲取多例項情況的出現。類似與採取了競爭資源(臨界資源的同步)   

3、靜態內部類

public class StaticSingleton implements Serializable{
    //構造器私有
    private StaticSingleton(){
    }

    public static synchronized StaticSingleton getInstance(){
        return  SingetonHolder.staticSingleton;
    }

    //使用靜態內部類來獲取例項
    /**
     * 這個實現思路中最主要的一點就是利用類中靜態變數的唯一性
     */
    private static class SingetonHolder{
            private static final StaticSingleton staticSingleton = new StaticSingleton();
     }
    }
}

三、單例模式和序列化

    單例模式可能在序列化的形式下被破壞,序列化的其實會呼叫該物件的無參構造器,建立一個新的物件,程式碼如下:

@Test
    public void testSignton(){
        StaticSingleton singleton = StaticSingleton.getInstance();
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\xieqixiu\\Desktop\\set.obj"))) {
            //物件序列化
            oos.writeObject(singleton);
        } catch (IOException e) {
            e.printStackTrace();
        }
        StaticSingleton singleton2 = null;
        //物件的反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\xieqixiu\\Desktop\\set.obj"))) {
            singleton2 = (StaticSingleton) ois.readObject();
            //打印出來發現序列化的物件 和原來的物件不是同一個物件違背了單例模式唯一例項
            log.info( "projectPoint==projectPoint2 : {}",singleton==singleton2);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

 ois.readObject() 方法

  public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            //執行該方法獲取物件 
            Object obj = readObject0(false);           
            }
}

 readObject0(false) 方法

 /**
     * Underlying readObject implementation.
     */
    private Object readObject0(boolean unshared) throws IOException {
        //省略部分程式碼
        depth++;
        totalObjectRefs++;
        try {
            switch (tc) {
                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                //執行該方法        
                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));
           //省略部分程式碼
    }

checkResolve()方法

 private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            //判斷物件是否可以例項化,如果可以呼叫其中的無參構造器建立該物件例項
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            //呼叫該物件的實現的readResolve()方法 如果有的化   
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

在readOrdinaryObject方法中由於呼叫了obj.newInstance() 會重新建立一個物件, 為了避免這種情況有一個invokeReadResolve()方法(該方法需要序列化的物件編寫readResolve() 在該方法下重新返回這個物件資訊即可保持序列化和反序列化的例項唯一)

最終程式碼如下:

//所有單例類 新增該方法
private Object readResolve() {
        return singleton;
    }