设计模式之单例模式 ( Singleton Pattern )

文章目录

  • 一、概述
    • 特征
    • 单例模式分类
    • 实现步骤
    • 优点
    • 类图
  • 二、代码演示
    • 饿汉式
    • 懒汉式
    • 枚举式
    • 容器缓存式
    • ThreadLocal 线程
  • 三、使用 Idea 多线程调试单例模式
  • 四、为什么加上 `readResolve();` 方法可以防止序列化破坏?
  • 五、为什么枚举单例模式能够防止序列化和反射破坏?

一、概述

单例模式是确保一个类在任何情况下都只有一个实例, 并自行实例化向整个系统提供一个全局的访问点.


特征

  1. 只能有一个实例;
  2. 必须自己创建自己的唯一实例;
  3. 必须向整理系统提供这个实例;

单例模式分类

  • 饿汉式单例;
  • 懒汉式单例;
  • 注册式单例;
  • ThreadLocal 线程式单例;
  • 枚举单例;

实现步骤

  1. 私有构造方法, 防止多个实例的产生;
  2. 私有静态实例变量, 保证不被外面置空和唯一;
  3. 公共的静态访问方法, 提供自己创建好的实例;

优点

  1. 某些类创建比较频繁, 对于一些大型对象的创建是一笔很大的系统开销.
  2. 省去了 new 操作符, 降低了系统内存的使用频率, 减轻 GC 压力;
  3. 可以保证内存中只有一个实例, 减少了内存开销, 可以避免对资源的多重占用

类图

设计模式之单例模式 ( Singleton Pattern )_第1张图片

二、代码演示

饿汉式

  • 特点 : 在类加载的时候就立即初始化,并且创建单例对象. 绝对线程安全,在线程还没出现以前实例化了, 不可能存在访问安全问题, 并且没有加锁, 它的执行效率比较高. 但因为类加载的时候就初始化了, 不管用不用都会占用内存空间, 形成内存空间的浪费.
/**
 * @Author: CaoJun
 * @Description: 饿汉式单例模式
 * @Create: 2020-01-17 20:09
 **/
public class HungrySingleton implements Serializable {

    private static final long serialVersionUID = -8686933389819315943L;

    /**
     * 1. 私有静态实例变量,保证唯一和不被外面置空(饿汉式:一开始就实例化对象)
     */
    private static final HungrySingleton HUNGRY_SINGLETON_INSTANCE = new HungrySingleton();

    /**
     * 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
     */
    private HungrySingleton() {
        if (HUNGRY_SINGLETON_INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    /**
     * 3. 公有的静态访问方法, 向整个系统提供全局访问点
     */
    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON_INSTANCE;
    }

    /**
     * 4. 防止序列化: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    private Object readResolve() {
        return HUNGRY_SINGLETON_INSTANCE;
    }
}
  • 饿汉式静态代码块
/**
 * @Author: CaoJun
 * @Description: 饿汉式静态代码块单例模式
 * @Create: 2020-01-17 20:13
 **/
public class HungryStaticSingleton  implements Serializable {

    private static final long serialVersionUID = -4191127512435945546L;

    /**
     * 1. 私有静态实例变量,保证唯一和不被外面置空(饿汉式:一开始就实例化对象)
     */
    private static HungryStaticSingleton HUNGRY_STATIC_SINGLETON_INSTANCE = null;

    // 2. 静态代码块实例化
    static {
        HUNGRY_STATIC_SINGLETON_INSTANCE = new HungryStaticSingleton();
    }

    /**
     * 3. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
     */
    private HungryStaticSingleton() {
        if (HUNGRY_STATIC_SINGLETON_INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    /**
     * 4. 公有的静态访问方法, 向整个系统提供全局访问点
     */
    public HungryStaticSingleton getInstance() {
        return HUNGRY_STATIC_SINGLETON_INSTANCE;
    }

     /**
     * 5. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    private Object readResolve() {
        return HUNGRY_STATIC_SINGLETON_INSTANCE;
    }
}

懒汉式

  • 特点: 被外部类调用的时候才会加载创建实例, 但是会有线程安全问题, 需要使用 synchronized 加锁, 这样就会有性能上的问题
/**
 * @Author: CaoJun
 * @Description: 懒汉式单例模式
 * @Create: 2020-01-17 20:16
 **/
public class LazySingleton implements Serializable {

    private static final long serialVersionUID = 8580274347085357039L;

    /**
     * 1. 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载
     */
    private static LazySingleton LAZY_SIMPLE_SINGLETON_INSTANCE = null;

    /**
     * 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
     */
    private LazySingleton() {
        if (LAZY_SIMPLE_SINGLETON_INSTANCE != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    /**
     * 3. 公有的静态访问方法, 创建实例, 向整个系统提供全局访问点
     */
    public static LazySingleton getInstance() {
        // 提高效率
        if (LAZY_SIMPLE_SINGLETON_INSTANCE == null) {
            // 保证线程安全
            synchronized (LazySingleton.class) {
                if (LAZY_SIMPLE_SINGLETON_INSTANCE == null) {
                    LAZY_SIMPLE_SINGLETON_INSTANCE = new LazySingleton();
                }
            }
        }
        return LAZY_SIMPLE_SINGLETON_INSTANCE;
    }

    /**
     * 4. 防止序列化: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    private Object readResolve() {
        return LAZY_SIMPLE_SINGLETON_INSTANCE;
    }
}
  • 懒汉式单例测试
/**
 * @Author: CaoJun
 * @Description: 
 * @Create: 2020-01-17 22:04
 **/
public class ExecutorThread implements Runnable {

    public void run() {
        LazySingleton singleton = LazySingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + singleton);
    }
}

/**
 * @Author: CaoJun
 * @Description: LazySingleton 测试
 * @Create: 2020-01-17 22:03
 **/
public class LazySingletonTest {

    public static void main(String[] args) {

        Thread t1 = new Thread(new ExecutorThread());
        Thread t2 = new Thread(new ExecutorThread());
        t1.start();
        t2.start();

        System.out.println("End");
    }
}
  • 懒汉式静态内部类
    • 特点: 兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题
/**
 * @Author: CaoJun
 * @Description: 懒汉式内部类单例模式
 * @Create: 2020-01-17 20:21
 **/
public class LazyInnerClassSingleton implements Serializable {


    private static final long serialVersionUID = -7388698081929929093L;

    /**
     * 1. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
     */
    private LazyInnerClassSingleton() {
        if (LazyHolder.LAZY_INNER_CLASS_SINGLETON != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    /**
     * 2. 向整个系统提供全局访问点
     */
    public static LazyInnerClassSingleton getInstance() {
        return LazyHolder.LAZY_INNER_CLASS_SINGLETON;
    }

    /**
     * 3. 此处使用一个内部类来维护单例
     */
    private static class LazyHolder {
        private static LazyInnerClassSingleton LAZY_INNER_CLASS_SINGLETON = new LazyInnerClassSingleton();
    }

    /**
     * 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    private Object readResolve() {
        return getInstance();
    }
}

枚举式

  • 特点: 防止反射和序列化破坏
/**
 * @Author: CaoJun
 * @Description: 注册式枚举单例模式
 * @Create: 2020-01-17 20:30
 **/
public 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;
    }
}

容器缓存式

  • 特点: 适用于创建实例非常多的情况,便于管理。但是,是非线程安全的
/**
 * @Author: CaoJun
 * @Description: 容器缓存方式单例模式
 * @Create: 2020-01-17 23:15
 **/
public class ContainerSingleton implements Serializable {

    private static final long serialVersionUID = -7388698631929929093L;
    
    /**
     * 1. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
     */
    private ContainerSingleton() {}
	
    /**
     * 2. 创建 Map 容器
     */
    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    /**
     * 3. 向整个系统提供全局访问点
     */
    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
    
    /**
     * 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    private Object readResolve() {
        return getInstance();
    }
}

ThreadLocal 线程

  • 特点: ThreadLocal 是将所有的对象全部都放在 ThreadLocalMap 中, 为每一个线程都提供一个对象, 实际上是以空间换时间来实现线程间的隔离的.
/**
 * @Author: CaoJun
 * @Description: ThreadLocal 线程单例模式
 * @Create: 2020-01-17 20:32
 **/
public class ThreadLocalSingleton {

    /**
     * 1. 使用 ThreadLocal 线程方式创建私有静态实例变量,保证唯一和不被外面置空
     */
    private static final ThreadLocal<ThreadLocalSingleton> SINGLETON_THREAD_LOCAL = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    /**
     * 2. 私有化构造方法, 防止多个实例产生, 判空(防止反射破坏)
     */
    private ThreadLocalSingleton() {
        if (SINGLETON_THREAD_LOCAL.get() != null) {
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    /**
     * 3. 公有的静态访问方法, 向整个系统提供全局访问点
     */
    public static ThreadLocalSingleton getInstance() {
        return SINGLETON_THREAD_LOCAL.get();
    }
    
     /**
     * 4. 防止序列化破坏: 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
     */
    private Object readResolve() {
        return getInstance();
    }
}

三、使用 Idea 多线程调试单例模式

设计模式之单例模式 ( Singleton Pattern )_第2张图片

设计模式之单例模式 ( Singleton Pattern )_第3张图片

设计模式之单例模式 ( Singleton Pattern )_第4张图片

设计模式之单例模式 ( Singleton Pattern )_第5张图片

设计模式之单例模式 ( Singleton Pattern )_第6张图片

设计模式之单例模式 ( Singleton Pattern )_第7张图片

设计模式之单例模式 ( Singleton Pattern )_第8张图片

设计模式之单例模式 ( Singleton Pattern )_第9张图片

四、为什么加上 readResolve(); 方法可以防止序列化破坏?

  • 当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当 于破坏了单例. 加上 readResolve(); 方法可以防止序列化.

  • 源码解释

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * @author CaoJun
 * @Description: 序列化破坏单例模式
 * @Create: 2020-01-17 22:36
 */
public class LazySingletonTest {
    public static void main(String[] args) {
        LazySingleton s1 = null;
        LazySingleton s2 = LazySingleton.getInstance();
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tmp.obj"));
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("tmp.obj"));) {

            oos.writeObject(s2);
            oos.flush();
            // 进 入 ObjectInputStream 类的 readObject()方法
            s1 = (LazySingleton) ois.readObject();

            System.out.println(s1 + "\r\n" + s2 + "\r\n" + (s1 == s2));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 发现在 readObject 中又调用了我们重写的 readObject0() 方法, 进入 readObject0(); 方法
/**
 * 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();
            }
        }
    }
  • 看到 TC_OBJECTD 中判断,调用了 ObjectInputStream#readOrdinaryObject() 方法
/**
 * ObjectInputStream#readObject0()
 */
private Object readObject0(boolean unshared) throws IOException {
	// ......
    case TC_OBJECT:
    return checkResolve(readOrdinaryObject(unshared));
	// ......
}
  • 发现调用了 ObjectStreamClass#isInstantiable() 方法
/**
 * ObjectStreamClass#readOrdinaryObject()
 */
private Object readOrdinaryObject(boolean unshared)
    // ... 
    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);
    }
	// ...
    if (obj != null &&
        // 调用了 hasReadResolveMethod() 方法
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        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;
}
  • 判断一下构造方法是否为空,构造方法不为空就返回 true
/**
 * ObjectStreamClass#isInstantiable()
 */
boolean isInstantiable() { 
    requireInitialized(); 
    return (cons != null); 
}
  • 判断构造方法是否存在之后,又调用了 hasReadResolveMethod() 方法, 判断是否为空,不为空就返回 true
boolean hasReadResolveMethod() { 
    requireInitialized(); 
    return (readResolveMethod != null); 
}
  • 通过全局搜索, 在ObjectStreamClass 中找到 readResolve 的赋值, 通过反射找到一个无参的 readResolve() 方法,并且保存下来
/**
 * ObjectStreamClass#ObjectStreamClass()
 */
private ObjectStreamClass(final Class<?> cl) {
	// ...
    readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
	// ...
}
  • invokeReadResolve() 方法中用反射调用了 readResolveMethod()方法, 通过 JDK 源码分析我们可以看出,虽然增加 readResolve() 方法返回实例,解决了单例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两次,只不过新创建的对象没有被返回而已, 该对象会被 GC 回收.
/**
 * ObjectStreamClass#invokeReadResolve()
 */
Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
{
    requireInitialized();
    if (readResolveMethod != null) {
        try {
            // 
            return readResolveMethod.invoke(obj, (Object[]) null);
        } catch (InvocationTargetException ex) {
            Throwable th = ex.getTargetException();
            if (th instanceof ObjectStreamException) {
                throw (ObjectStreamException) th;
            } else {
                throwMiscException(th);
                throw new InternalError(th);  // never reached
            }
        } catch (IllegalAccessException ex) {
            // should not occur, as access checks have been suppressed
            throw new InternalError(ex);
        }
    } else {
        throw new UnsupportedOperationException();
    }
}

五、为什么枚举单例模式能够防止序列化和反射破坏?

  1. 下载 jad 工具, 解压后配置好环境变量, 就可以使用命令行调用了;
  2. 找到工程所在的 class 目录,复制 EnumSingleton.class 所在的路径

设计模式之单例模式 ( Singleton Pattern )_第10张图片

  1. 使用命令行, 输入命令 jad 后面输入复制好的路径,在 class 目录下会多一个 EnumSingleton.jad 文件。打开 EnumSingleton.jad 文件, 有如下静态代码块: 枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现
static {
    INSTANCE = new EnumSingleton("INSTANCE", 0); 
    $VALUES = (new EnumSingleton[] { INSTANCE }); 
}
  1. 关于序列化的代码: 在 readObject0() 中调用了 readEnum() 方法,来看 readEnum() 中代码实现
/**
 * ObjectInputStream#readObject()
 */
private Object readObject0(boolean unshared) throws IOException { 
    // ... 
    case TC_ENUM: 
    	return checkResolve(readEnum(unshared)); ... 
}
  1. 发现枚举类型其实通过类名和 Class对象类找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次
/**
 * ObjectInputStream#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;
    }
  1. 运行枚举类型的单例测试, 发现报出 java.lang.NoSuchMethodException 异常, 打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected 的构造方法
/**
 * java.lang.Enum
 */
protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}
  1. 测试
/**
 * @Author: CaoJun
 * @Description:
 * @Create: 2020-01-17 23:10
 **/
public class EnumTest {
    public static void main(String[] args) {
        try {
            Class clazz = EnumSingleton.class;
            Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
            c.setAccessible(true);
            EnumSingleton enumSingleton = (EnumSingleton) c.newInstance("Tom", 666);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 执行结果: 告诉我们不能用反射来创建枚举类型

设计模式之单例模式 ( Singleton Pattern )_第11张图片

  1. 进入 JDK 源码中的 Constructor#newInstance() 方法中做了强制性的判断,如果修饰符是Modifier.ENUM 枚举类型, 直接抛出异常
/**
 * Constructor#newInstance()
 */
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;
}

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