栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java 设计模式之单例模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java 设计模式之单例模式

单例模式

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

单例模式有 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 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 valueOf(Class 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 enumConstantDirectory() {
    if (enumConstantDirectory == null) {
        // 这个就是存储实例的数组了吧,看见曙光了。接着往下看吧
        T[] universe = getEnumConstantsShared();
        if (universe == null)
            throw new IllegalArgumentException(
            getName() + " is not an enum type");
        Map 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() {
                    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;
    }

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/763631.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号