单例模式

Posted 醉梦了红尘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。

定义

单例模式,是为了在任何情况下都绝对只能有一个实例,并且提供一个全局访问点。单例模式属于创造型模式。

饿汉式单例模式

1.使用private修饰的静态成员属性直接初始化,利用jvm类加载机制实现单例

2.构造函数私有化,防止外部new

3.提供全局入口,获取单例实例

public class HugrySingleTon {
    //private修饰静态成员属性初始化 利用jvm的类加载机制实现单例
    private static final HugrySingleTon hugrySingleTon = new HugrySingleTon();

    //构造函数私有化,防止外部调用 无法避免反射破坏
    private HugrySingleTon() {
    }

    //提供全局入口获取单例实例
    public static HugrySingleTon getHugryInstance(){
        return HugrySingleTon.hugrySingleTon;
    }
}

静态代码块实现方式

public class HugrySingleTon {
    //private修饰静态成员属性初始化 利用jvm的类加载机制实现单例
    private static final HugrySingleTon hugrySingleTon;
    
    static {
      hugrySingleTon = new HugrySingleTon()
    }

    //构造函数私有化,防止外部调用 无法避免反射破坏
    private HugrySingleTon() {
    }

    //提供全局入口获取单例实例
    public static HugrySingleTon getHugryInstance(){
        return HugrySingleTon.hugrySingleTon;
    }
}

优点: 写法简单,容易理解,能够保证线程安全,执行效率高,适用于单例对象较少的情况

缺点: 所有对象在容器初始化的时候完成初始化,会降低容器的启动速度,如果容器中存在大量的单例对象,还会对容器的内存造成较大的浪费。

懒汉式单例

为了解决饿汉式带来的内存浪费问题,出现了懒汉式单例的写法。

不加锁

public class LazySingleTon {
    private static LazySingleTon lazySingleTon;

    private LazySingleTon(){}

    public static LazySingleTon getInstance(){
        if(lazySingleTon == null) {
            lazySingleTon = new LazySingleTon();
        }
        return lazySingleTon;
    }
}

问题: 线程不安全,在多线程情况下,多个线程同时进入if方法同时满足条件,导致每次都创建了新的对象,违反了单例的定义

外层加锁

public class LazySingleTon {
    private static LazySingleTon lazySingleTon;

    private LazySingleTon() {
    }

    public static LazySingleTon getInstance() {
        synchronized (LazySingleTon.class) {
            if (lazySingleTon == null) {
                lazySingleTon = new LazySingleTon();
            }
            return lazySingleTon;
        }
    }
}

问题: 线程安全,但是如果已经创建了对象,进去直接加锁,导致无用的加锁竞争,会浪费cpu资源

双重校验锁

public class LazySingleTon {
    private static volatile LazySingleTon lazySingleTon;

    private LazySingleTon() {
    }

    public static LazySingleTon getInstance() {
        if(lazySingleTon != null){
            return lazySingleTon;
        }
        synchronized (LazySingleTon.class) {
            if (lazySingleTon == null) {
                lazySingleTon = new LazySingleTon();
            }
            return lazySingleTon;
        }
    }
}

注意需要加volatile关键字防止指令重排序,导致DCL问题,拿到不完整的实例

破坏单例

反射破坏单例

直接通过反射拿到私有构造,获取对象,会破坏单例,newInstance本质上会调用无参构造,因此需要在无参构造函数中加入判断

public class HugrySingleTon {
    //private修饰静态成员属性初始化 利用jvm的类加载机制实现单例
    private static final HugrySingleTon hugrySingleTon = new HugrySingleTon();

    //构造函数私有化,防止外部调用 无法避免反射破坏
    private HugrySingleTon() {
        if(hugrySingleTon != null) {
            throw new RuntimeException("实例已存在!");
        }
    }

    //提供全局入口获取单例实例
    public static HugrySingleTon getHugryInstance(){
        return HugrySingleTon.hugrySingleTon;
    }

    public static void main(String[] args) throws Exception {
        HugrySingleTon hugryInstance1 = HugrySingleTon.getHugryInstance();
        HugrySingleTon hugryInstance2 = HugrySingleTon.getHugryInstance();
        System.out.println(hugryInstance1 == hugryInstance2);
        System.out.println("************************************************");
        Constructor<HugrySingleTon> constructor = HugrySingleTon.class.getDeclaredConstructor();
        HugrySingleTon hugrySingleTon3 = constructor.newInstance();
        System.out.println(hugryInstance1 == hugrySingleTon3);
    }
}

序列化破坏单例

public class HugrySingleTon implements Serializable{
    //private修饰静态成员属性初始化 利用jvm的类加载机制实现单例
    private static final HugrySingleTon hugrySingleTon = new HugrySingleTon();

    //构造函数私有化,防止外部调用 无法避免反射破坏
    private HugrySingleTon() {
        if(hugrySingleTon != null) {
            throw new RuntimeException("实例已存在!");
        }
    }

    //提供全局入口获取单例实例
    public static HugrySingleTon getHugryInstance(){
        return HugrySingleTon.hugrySingleTon;
    }
	/**
	* 放开readResolve方法可以防止序列化破坏单例
    private Object readResolve(){
        return hugrySingleTon;
    }
    **/

    public static void main(String[] args) throws Exception {
        HugrySingleTon hugryInstance1 = HugrySingleTon.getHugryInstance();
        HugrySingleTon hugryInstance2 = HugrySingleTon.getHugryInstance();
        System.out.println(hugryInstance1 == hugryInstance2);
        System.out.println("************************************************");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("serialized.obj"));
        out.writeObject(hugryInstance1);
        out.flush();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialized.obj"));
        HugrySingleTon hugryInstance3 = (HugrySingleTon) in.readObject();
        System.out.println(hugryInstance1 == hugryInstance3);
        out.close();
        in.close();
    }
}

执行结果

image-20210622095008051

源码分析
//java.io.ObjectInputStream#readObject(java.lang.Class<?>)
private final Object readObject(Class<?> type)
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        if (! (type == Object.class || type == String.class))
            throw new AssertionError("internal error");

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(type, false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

private Object readObject0(Class<?> type, boolean unshared) throws IOException {
       ...
        try {
            switch (tc) {
               
                case TC_OBJECT:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an object to java.lang.String");
                    }
                    return checkResolve(readOrdinaryObject(unshared));

             
            }
        }
    ...
}

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {...
        Object obj;
        try {  
            //实例化对象 desc是ObjectStreamClass newInstance不会走无参构造所以靠无参构造函数无法实现防止单例破坏
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        ...

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            //判断对象是否有readResovle的无参方法
            desc.hasReadResolveMethod())
        {
            //执行readResovle方法,将返回值作为序列化结果
            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;
    }

通过在序列化对象中增加

 private Object readResolve(){
     //返回原始单例实例
        return hugrySingleTon;
 }

通过程序判断是否有readResovle,将序列化的结果,强制替换为了原始单例对象,实际上还是创建了新的对象只是没有返回。浪费了内存

注册式单例

将每一个实例都登记在某一个地方,使用唯一标识来获取实例。

枚举式单例

enum EnumSingleton {
    INSTANCE;
    private Object data;

    public Object getData(){
        return data;
    }

    public void  setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }

}
public class EnumSingletonTest {
    public static void main(String[] args) throws Exception{
        EnumSingleton originInstatnce = EnumSingleton.getInstance();
        originInstatnce.setData(new Object());
        FileOutputStream fo = new FileOutputStream("serialized.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fo);
        oos.writeObject(originInstatnce);
        oos.flush();
        FileInputStream fi = new FileInputStream("serialized.obj");
        ObjectInputStream ois = new ObjectInputStream(fi);
        EnumSingleton serializedInstatnce = (EnumSingleton) ois.readObject();
        fo.close();
        oos.close();
        fi.close();
        ois.close();
        System.out.println(originInstatnce == serializedInstatnce);
        System.out.println(originInstatnce.getData() == originInstatnce.getData());
    }
}

枚举式单例,是饿汉式单例的一种实现

枚举式单例无法被序列化破坏的原因:
 private Object readObject0(Class<?> type, boolean unshared) throws IOException {
       ...
        try {
            switch (tc) {
               ...
                case TC_ENUM:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an enum to java.lang.String");
                    }
                    return checkResolve(readEnum(unshared));

               ...
    }
       
private Enum<?> readEnum(boolean unshared) throws IOException {
    if (bin.readByte() != TC_ENUM) {
        throw new InternalError();
    }

    ObjectStreamClass desc = readClassDesc(false);
    if (!desc.isEnum()) {
        throw new InvalidClassException("non-enum class: " + desc);
    }

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

    String name = readString(false);
    Enum<?> result = null;
    Class<?> cl = desc.forClass();
    if (cl != null) {
        try {
            @SuppressWarnings("unchecked")
            //通过Enum.valueOf方法 直接从类名和类对象从枚举中提取对应的枚举对象
            //不存在通过newInstance多次构造的情况
            Enum<?> en = Enum.valueOf((Class)cl, name);
            result = en;
        } catch (IllegalArgumentException ex) {
            throw (IOException) new InvalidObjectException(
                "enum constant " + name + " does not exist in " +
                cl).initCause(ex);
        }
        if (!unshared) {
            handles.setObject(enumHandle, result);
        }
    }

    handles.finish(enumHandle);
    passHandle = enumHandle;
    return result;
}

枚举防止反射单例破坏

枚举的源码只提供了有参构造,并且在有参构造中做了单例的判断逻辑

容器式单例

public class ContainerSingleton {
    private ContainerSingleton() {
    }

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

    public static Object getBean(String className) {
        synchronized (ioc) {
            Object obj = null;
            if(!ioc.containsKey(className)) {
                try{
                    obj = Class.forName(className);
                    ioc.put(className, obj);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }else{
                 obj = ioc.get(className);
            }
            return obj;
        }
    }
}

ThreadLocal

线程安全的缓存对象,ThreadLocal只是作为操作线程对象的入口,包括设置初始值,get() ,set(),

实质上,每个Thread对象都维护这自己的ThreadLocalMap的成员属性,我们对象ThreadLocal的操作,实质上是对正在执行的线程的ThreadLocalMap的get和set,所以先对ThreadLocal的操作是线程安全的

以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

片段作为 Android 中的单例

单例片段或保存网页视图状态

你熟悉的设计模式都有哪些?写出单例模式的实现代码

单例模式以及静态代码块