java 设计模式之单例模式

单例模式

目录

单例模式

单例模式的结构与实现

饿汉模式(最简单的)

懒汉式 

双重检查锁(DCL)

静态嵌套类(静态内部类)

依赖注

枚举

关于破坏单例的两种方式

序列化

反射

避免方式


单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例模式有 3 个特点:

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点;

单例模式的结构与实现

单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

饿汉模式(最简单的)

public class HungrySingleton {

    // 类一旦加载就创建一个单例
    private static HungrySingleton hungrySingleton = new HungrySingleton();
	// 构造私有化,避免类在外部被实例化
    private HungrySingleton() {}
	// 对外提供唯一的获取途径
    public static HungrySingleton getIns() {
        return hungrySingleton;
    }
}

懒汉式 

public class LazySingleton {

    private static LazySingleton lazySingleton;
	// 构造私有化,避免类在外部被实例化
    private LazySingleton() {}
	// 对外提供唯一的获取途径
    // 直接方法加锁(效率不高)
    public synchronized static LazySingleton getIns() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
    
}

双重检查锁(DCL)

public class DoubleCheckSingleton {


    private static DoubleCheckSingleton doubleCheckSingleton;
	// 构造私有化,避免类在外部被实例化
    private DoubleCheckSingleton() {}

    public static DoubleCheckSingleton getIns() {
        if (doubleCheckSingleton == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (doubleCheckSingleton == null) {
                    doubleCheckSingleton = new DoubleCheckSingleton();
                }
            }
        }
        return doubleCheckSingleton;
    }

}

这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。

指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。

doubleCheckSingleton = new DoubleCheckSingleton() 可分解为如下伪代码:

memory = allocate();   //1:分配对象的内存空间
ctorInstance(memory);  //2:初始化对象
instance = memory;     //3:设置instance指向刚分配的内存地址

但是经过重排序后如下:

memory = allocate();   //1:分配对象的内存空间
instance = memory;     //3:设置instance指向刚分配的内存地址
                       //注意,此时对象还没有被初始化!
ctorInstance(memory);  //2:初始化对象

在多线程情况下可能会出现 线程A执行了instance = memory; 此时线程B 发现 doubleCheckSingleton不为空,随即直接返回。

可以使用volatile变量禁止指令重排序,让DCL生效:

public class DoubleCheckSingleton {

	// 添加 volatile 关键字 防止指令重排序
    private volatile static DoubleCheckSingleton doubleCheckSingleton;
	// 构造私有化,避免类在外部被实例化
    private DoubleCheckSingleton() {}

    public static DoubleCheckSingleton getIns() {
        if (doubleCheckSingleton == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (doubleCheckSingleton == null) {
                    doubleCheckSingleton = new DoubleCheckSingleton();
                }
            }
        }
        return doubleCheckSingleton;
    }

}

静态嵌套类(静态内部类)

public class StaticNestedSingleton {

    private StaticNestedSingleton() {}
    // 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
    private static class Holder {
        private static StaticNestedSingleton instance = new StaticNestedSingleton();
    }
    public static StaticNestedSingleton getIns() {
        return Holder.instance;
    }
}

依赖注

public class DependencyInjectionSingleton {

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

    private DependencyInjectionSingleton(){}

    public synchronized static Object getBean(String name) {
        if (!ioc.containsKey(name)) {
            try {
                Class<?> clazz = Class.forName(name);
                Constructor<?> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);
                Object o = constructor.newInstance();
                ioc.put(name, o);
                return o;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return ioc.get(name);
    }

}

枚举

public enum EnumSingleton {

    /**
     * 枚举实例
     */
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

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

    public static EnumSingleton getIns() {
        return INSTANCE;
    }

}

关于破坏单例的两种方式

序列化

类在实现了序列化的情况下,我们则可以通过序列化和反序列化达到破坏单例模式

反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。

反射

尽管构造器私有化,我们还是可以通过反射的方式获取构造方法,实例化一个新的对象。

避免方式

使用枚举的方式,可以有效避免这种情况 。

那为什么枚举可以避免这两种破坏方式呢?

实现序列化的枚举代码:

public enum EnumSingleton implements Serializable {

    /**
     * 枚举实例
     */
    INSTANCE, INSTANCE_1;

    private Object data;

    public Object getData() {
        return data;
    }

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

    public static EnumSingleton getIns() {
        return INSTANCE;
    }
}

通过JAD反编译工具,看看实际的源码 

public final class EnumSingleton extends Enum
    implements Serializable
{

    // 反序列化会调用这个方法 返回新的数组。实例引用则是相同的实例
    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(create/singleton/enums/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

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

    public static EnumSingleton getIns()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    public static final EnumSingleton INSTANCE_1;
    private Object data;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        INSTANCE_1 = new EnumSingleton("INSTANCE_1", 1);
        $VALUES = (new EnumSingleton[] {
            INSTANCE, INSTANCE_1
        });
    }
}

实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum

public static final EnumSingleton INSTANCE;

public static final EnumSingleton INSTANCE_1;

private static final EnumSingleton $VALUES[];

static 
{
    // 类加载是就初始化
    INSTANCE = new EnumSingleton("INSTANCE", 0);
    INSTANCE_1 = new EnumSingleton("INSTANCE_1", 1);
    $VALUES = (new EnumSingleton[] {
        INSTANCE, INSTANCE_1
    });
}

这段代码,我们可以看出来枚举使用的饿汉式模式来实例化的,这是在jvm层面上替我们实现的,相信效率会更高效。

说了半天还是没有说到点子上,别慌:happy: 。我们接着来

先分析反序列化

反序列化是通过 ObjectOutputStream.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();
        }
    }
}

private Object readObject0(boolean unshared) throws IOException {
    // 。。。省略
    // 我们只看和枚举有关的
    case TC_ENUM:
    	// 在接着
    	return checkResolve(readEnum(unshared));
    // 。。。省略
}
private Enum<?> readEnum(boolean unshared) throws IOException {
    // 。。。省略
    // 找到获取枚举的方法了,还得继续呀。。
    Enum<?> en = Enum.valueOf((Class)cl, name);
    // 。。。省略
}

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
    // 看这个 enumConstantDirectory
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}

Map<String, T> enumConstantDirectory() {
    if (enumConstantDirectory == null) {
        // 这个就是存储实例的数组了吧,看见曙光了。接着往下看吧
        T[] universe = getEnumConstantsShared();
        if (universe == null)
            throw new IllegalArgumentException(
            getName() + " is not an enum type");
        Map<String, T> m = new HashMap<>(2 * universe.length);
        for (T constant : universe)
            m.put(((Enum<?>)constant).name(), constant);
        enumConstantDirectory = m;
    }
    return enumConstantDirectory;
}
// 终于是让我们找到了
T[] getEnumConstantsShared() {
    if (enumConstants == null) {
        if (!isEnum()) return null;
        try {
            // 看到这儿没,反编译中枚举的方法 通过反射调用的 
            // 到此我们,就回到最开始反编译代码中的 values()方法了
            // return $VALUES.clone() 数组对象
            final Method values = getMethod("values");
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Void>() {
                    public Void run() {
                        values.setAccessible(true);
                        return null;
                    }
                });
            @SuppressWarnings("unchecked")
            T[] temporaryConstants = (T[])values.invoke(null);
            enumConstants = temporaryConstants;
        }
        // These can happen when users concoct enum-like classes
        // that don't comply with the enum spec.
        catch (InvocationTargetException | NoSuchMethodException |
               IllegalAccessException ex) { return null; }
    }
    return enumConstants;
}

通过这个代码流程,可以得到。反序列化最终得到还是,最开始初始化的实例

再看看反射

try {
    Class<?> clazz = EnumSingleton.getIns().getClass();
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
    constructor.setAccessible(true);
    // 反编译知道构造器,两个参数分别代表 实例对象的引用名称,和位置。
    Object o = constructor.newInstance("INSTANCE", 0);
    System.out.println(o);
    System.out.println(EnumSingleton.getIns());
} catch (Exception e) {
    e.printStackTrace();
}

// 我们再接着看吧
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);
            }
        }
        // 咯,看到没,看到没。在jdk层面就已经不允许我们通过反射构造枚举对象了
        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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值