单例模式

单例模式

  • 1、单例模式的定义
  • 2、单例模式的几种经典实现
    • 2.1、饿汉式
    • 2.2、懒汉式
    • 2.3、静态内部类
    • 2.4、枚举式
    • 2.5、容器式
    • 2.6、线程内的单例
  • 3、单例模式的注意事项
    • 3.1、反射破坏单例
    • 3.2、序列化破坏单例
  • 4、单例模式在源码中的应用

1、单例模式的定义

单例模式(Singleton Pattern)是指一个类在任何时候都确保只有一个实例,并提供一个全局访问点。属于创建型模式。

2、单例模式的几种经典实现

2.1、饿汉式

类加载时就创建当前类的对象,对象没有经过方法而创建,不存在线程共享的问题也就不存在线程安全问题。

public class HungrySingleton {

    // 私有化构造器,放在在外面用new创建对象
    private HungrySingleton() {}

    // 类加载时创建对象
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    // 提供全局访问点
    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

优点:实现简单,容易理解,线程安全;
缺点:类加载时就创建对象,耗费内存空间、会被反射破坏;

2.2、懒汉式

懒汉式单例在类加载时并不实例化对象,在调用的时候才真正实例化对象。

public class LazySingleton {

    // 私有化构造器,方法在外部通过new创建对象
    private LazySingleton() {}

    // 只声明对象的引用,不做具体实现
    private volatile static LazySingleton instance;

    // 提供全局访问点,实例化对象
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

优点:程序调用时才实例化对象,防止内存浪费;
缺点:用双重检查保证线程的安全,增加了程序的复杂性,不容易理解;加锁保证线程的安全,会有一定的效率问题;会被反射破坏;
注意:声明对象的引用时的volatile关键字是必须要加的,为了防止CPU指令的重排序。
线程的切换是发生在CPU指令之间的,每个指令执行前后都有可能发生线程的切换,创建对象的正常过程是:
1)分配一块内存;
2)在内存中实例化对象;
3)将内存地址指向对象的引用;
如果发生指令优化,就有可能是:
1)分配一块内存;
2)将内存地址指向对象的引用;
3)在内存中实例化对象;
多线程情况下,先判断 instance == null 此时获取锁,JVM保证只有一个线程拿到锁,再判断 instance == null,此时实例化对象,若在指令优化的第2步与第3步之间发生线程切换,第2个线程先判断 instance == null?发现 instance != null,因为此时对象的引用已经有了内存的指向,于是直接return instance,但是当调用的instance的具体方法时,则会发生空指针异常,因为对象并没有真正实例化。

2.3、静态内部类

利用内部类的懒加载机制,实现单例的懒加载

public class StaticInnerSingleton {

    // 私有化构造器,防止在外部通过new创建对象
    private StaticInnerSingleton() {}

    // 提供全局访问点
    public static StaticInnerSingleton getInstance() {
        return InnerHolder.INSTANCE;
    }

    // 在静态内部类中创建单例对象
    private static class InnerHolder {
        static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
    }
}

优点:利用java的语法,保证线程安全的同时也保证了懒加载;
缺点:会被反射破坏;

2.4、枚举式

public enum EnumSingleton {

    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

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

    private static EnumSingleton getInstance() {
        return INSTANCE;
    }
}

优点:实现简单,线程安全,不会被反射破坏,也不会被反序列化破坏;
缺点:属于饿汉式,同样会导致内存浪费;

枚举式利用java的语法保证了线程安全,并且其内部结构保证了其不会被反射破坏,具体如何做到的,后面会讲到。

2.5、容器式

public class ContainerSingleton {

    // 私有化构造器,防止在外部通过new创建对象
    private ContainerSingleton() {}

    // 创建实例容器
    private static final Map<String, Object> BEAN_MAP = new ConcurrentHashMap<String, Object>();

    // 获取实例的统一入口
    public static Object getBean(String className) {
        if (BEAN_MAP.containsKey(className)) {
            return BEAN_MAP.get(className);
        }
        synchronized (BEAN_MAP) {
            if (!BEAN_MAP.containsKey(BEAN_MAP)) {
                try {
                    Class<?> clazz = Class.forName(className);
                    Object bean = clazz.newInstance();
                    BEAN_MAP.put(className, bean);
                    return bean;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        return null;
    }
}

容器式单例是Spring在管理IOC容器时采用的实现方式,其在内部维护了一个容器,保存类的单例。容器是在类加载时就创建了,即使当前的ContainerSingleton被反射破坏或者被序列化破坏创建了多个对象,其内部的容器也只有一份。
优点:懒加载减少了内存的不必要浪费,不会被反射破坏,也不会被序列化破坏;
缺点:需要通过加锁保证线程安全,会有一定的效率问题;

2.6、线程内的单例

public class ThreadLocalSingleton {

    // 私有化构造器,防止在外部通过new创建对象
    private ThreadLocalSingleton() {}

    // 初始化ThreadLocal
    private static final ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    // 提供全局访问点
    public static ThreadLocalSingleton getInstance() {
        return threadLocal.get();
    }
}

线程内部单例虽无法保证全局只有一个实例,但是可以保证线程内部只有一个实例。

3、单例模式的注意事项

3.1、反射破坏单例

饿汉式、懒汉式以及静态内部类式的单例都是可以被反射所破坏的,具体是怎么实现的呢,看代码实例:

public class Test{
    public static void main(String[] args) {
        try {
            // 利用反射获取无参的构造器
            Constructor<StaticInnerSingleton> constructor = StaticInnerSingleton.class.getDeclaredConstructor();
            // 获取访问权限,破坏私有化的设置private
            constructor.setAccessible(true);
            // 通过构造器创建对象
            StaticInnerSingleton instance = constructor.newInstance();

            System.out.println(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

以静态内部类为例,简短的几行代码就通过反射的方式创建了一个新的对象,破坏了单例。
枚举式单例为什么不能被反射破坏呢,看代码实例:

public class Test{
    public static void main(String[] args) {
        try {
            Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            EnumSingleton instance = constructor.newInstance();
            System.out.println(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

java.lang.NoSuchMethodException: com.gpedu.test.singleton.EnumSingleton.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.gpedu.test.singleton.Test.main(Test.java:8)

错误提示没有无参的构造方法,所有枚举都是默认继承自java.lang.Enum类,跟踪下源码发现

protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

Enum类没有无参的构造器,只有一个带两个参数的构造器,调整测试代码:

public class Test{
    public static void main(String[] args) {
        try {
            Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
            constructor.setAccessible(true);
            EnumSingleton instance = constructor.newInstance("name", 1);
            System.out.println(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:416)
	at com.gpedu.test.singleton.Test.main(Test.java:10)

找到错误处的源码发现:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

枚举类是不允许通过反射创建对象的,在反射的底层已经杜绝了这种情况的发生,因此枚举式的单例无法被反射所破坏。

3.2、序列化破坏单例

对象序列化是将内存中的一个对象根据一定的序列化算法转换成二进制流,一是可以将对象持久化到磁盘中,二是用于在网络上传输。一些RPC调用在传输对象时,对象的类都需要实现Serializable接口。
饿汉式、懒汉式、静态内部类的方式都会被序列化破坏,但是枚举式不会被序列化破坏。
序列化是如何破坏单例的,解决办法是什么,看实例代码:
以饿汉式单例为例

public class SeriableSingleton implements Serializable {

    private SeriableSingleton() {}

    private static final SeriableSingleton INSTANCE = new SeriableSingleton();

    public static SeriableSingleton getInstance() {
        return INSTANCE;
    }
}
public class Test{
    public static void main(String[] args) {
        // 创建一个空的引用
        SeriableSingleton instance1 = null;
        // 获取单例的实例
        SeriableSingleton instance2 = SeriableSingleton.getInstance();

        // 声明文件输出流
        FileOutputStream fos = null;
        // 声明对象输出流
        ObjectOutputStream oos = null;
        // 声明文件输入流
        FileInputStream fis = null;
        // 声明对象输入流
        ObjectInputStream ois = null;

        try {
            // 实例化文件输出流
            fos = new FileOutputStream("SeriableSingleton.obj");
            // 实例化对象输出流
            oos = new ObjectOutputStream(fos);
            // 将instance2对象序列化后写入SeriableSingleton.obj文件中
            oos.writeObject(instance2);
            oos.flush();

            // 实例化文件输入流
            fis = new FileInputStream("SeriableSingleton.obj");
            // 实例化对象输入流
            ois = new ObjectInputStream(fis);
            // 将SeriableSingleton.obj文件中的内容读出来反序列化并赋值给instance1
            instance1 = (SeriableSingleton) ois.readObject();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (oos != null) {
                    oos.close();
                }
                if (ois != null) {
                    ois.close();
                }
                if (fos != null) {
                    fos.close();
                }
                if (fis != null) {
                    fis.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 打印两个对象
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

执行结果:

com.gpedu.test.singleton.SeriableSingleton@1befd9f
com.gpedu.test.singleton.SeriableSingleton@12c9b19

对象经过序列化与反序列化后,得到的对象与原对象并不相同,由此可见,序列化是会破坏单例的。那如何解决这个问题呢?
其实很简单,只需要在类内部实现一个方法,注意此方法的写法是固定的,写错一点都会影响结果,看示例代码:

public class SeriableSingleton implements Serializable {

    private SeriableSingleton() {}

    private static final SeriableSingleton INSTANCE = new SeriableSingleton();

    public static SeriableSingleton getInstance() {
        return INSTANCE;
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

再看执行结果:

com.gpedu.test.singleton.SeriableSingleton@12c9b19
com.gpedu.test.singleton.SeriableSingleton@12c9b19

序列化破坏单例的问题就这样被轻松搞定了,是不是感觉很神奇,那他内部的原理是什么呢,我们追踪一下源码
从ObjectInputStream的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);
            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();
            }
        }
    }

其返回的obj对象是通过readObject0方法得到的,我们再进去发现

private Object readObject0(boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }

        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }

        depth++;
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    return readHandle(unshared);

                case TC_CLASS:
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

                case TC_ARRAY:
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }

                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

对于普通对象,obj是通过readOrdinaryObject方法得到的,我们再进去发现

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())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }
ObjectStreamClass desc = readClassDesc(false);

这行代码创建了一个ObjectStreamClass的对象

desc.hasReadResolveMethod()

最下面的这行调用是判断是否有readResolve方法

boolean hasReadResolveMethod() {
        return (readResolveMethod != null);
    }

那ObjectStreamClass的readResolveMethod是什么时候创建的呢

private ObjectStreamClass(final Class<?> cl) {
        this.cl = cl;
        name = cl.getName();
        isProxy = Proxy.isProxyClass(cl);
        isEnum = Enum.class.isAssignableFrom(cl);
        serializable = Serializable.class.isAssignableFrom(cl);
        externalizable = Externalizable.class.isAssignableFrom(cl);

        Class<?> superCl = cl.getSuperclass();
        superDesc = (superCl != null) ? lookup(superCl, false) : null;
        localDesc = this;

        if (serializable) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (isEnum) {
                        suid = Long.valueOf(0);
                        fields = NO_FIELDS;
                        return null;
                    }
                    if (cl.isArray()) {
                        fields = NO_FIELDS;
                        return null;
                    }

                    suid = getDeclaredSUID(cl);
                    try {
                        fields = getSerialFields(cl);
                        computeFieldOffsets();
                    } catch (InvalidClassException e) {
                        serializeEx = deserializeEx =
                            new ExceptionInfo(e.classname, e.getMessage());
                        fields = NO_FIELDS;
                    }

                    if (externalizable) {
                        cons = getExternalizableConstructor(cl);
                    } else {
                        cons = getSerializableConstructor(cl);
                        writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
                        readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
                        hasWriteObjectData = (writeObjectMethod != null);
                    }
                    writeReplaceMethod = getInheritableMethod(
                        cl, "writeReplace", null, Object.class);
                    readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);
                    return null;
                }
            });
        } else {
            suid = Long.valueOf(0);
            fields = NO_FIELDS;
        }

        try {
            fieldRefl = getReflector(fields, this);
        } catch (InvalidClassException ex) {
            // field mismatches impossible when matching local fields vs. self
            throw new InternalError(ex);
        }

        if (deserializeEx == null) {
            if (isEnum) {
                deserializeEx = new ExceptionInfo(name, "enum type");
            } else if (cons == null) {
                deserializeEx = new ExceptionInfo(name, "no valid constructor");
            }
        }
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getField() == null) {
                defaultSerializeEx = new ExceptionInfo(
                    name, "unmatched serializable field(s) declared");
            }
        }
    }

ObjectStreamClass的私有化构造器中有一段

 readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);

此处便是初始化readResolve这个方法,我们跟进去看一下

private static Method getInheritableMethod(Class<?> cl, String name,
                                               Class<?>[] argTypes,
                                               Class<?> returnType)
    {
        Method meth = null;
        Class<?> defCl = cl;
        while (defCl != null) {
            try {
                meth = defCl.getDeclaredMethod(name, argTypes);
                break;
            } catch (NoSuchMethodException ex) {
                defCl = defCl.getSuperclass();
            }
        }

        if ((meth == null) || (meth.getReturnType() != returnType)) {
            return null;
        }
        meth.setAccessible(true);
        int mods = meth.getModifiers();
        if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) {
            return null;
        } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
            return meth;
        } else if ((mods & Modifier.PRIVATE) != 0) {
            return (cl == defCl) ? meth : null;
        } else {
            return packageEquals(cl, defCl) ? meth : null;
        }
    }

其实很简单,就是通过反射取cl(也就是SeriableSingleton对象)的无参的readResolve方法,若调用不报异常,则将结果返回。

到此我们便了解了整个过程 ,其实就是在反序列化时,若实现了readResolve方法,我们优先调用其readResolve方法 ,并将结果返回,若没有实现readResolve方法则将反序列化后的对象返回。
这种方式虽然可以保证返回的对象与原有的对象一致,但是反序列化后的对象已经创建只是没有用到而已,也会造成内存的浪费。

那对于枚举式的单例为何不会有序列化的问题呢,我们再回到readObject0方法,发现

case TC_ENUM:
                    return checkResolve(readEnum(unshared));

对于枚举类的对象,调用的是readEnum方法,我们进去后发现

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<?> 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;
    }

枚举其实是根据类名和类对象获得唯一一个枚举对象,因此其不会被创建多次。

4、单例模式在源码中的应用

java的运行时类Runtime

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

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {
    }

采用的是饿汉式的单例

你可能感兴趣的:(设计模式,设计模式,java)