单例模式

单例和静态类的区别

单例模式和静态类都具有良好的访问性,它们之间有许多相似之处,例如,两者可以直接使用而无须创建对象,都可提交唯一实例,在一个非常高的高度上看起来它们都为是用于同样的任务。
区别:

  1. 静态类比单例具有更好的性能,因为静态方法在编译期绑定。
  2. override的能力,因Java中的静态方法是不可以覆盖的,这就导致其木有太多的灵活性,另一面,你可通过继承的方式覆盖单例类中定义的方法。
  3. 静态类很难模拟,因此难于单例测试,单例更容易模拟,因为也比静态类易于编写单元测试,不论神马单例期望神马,你都可以传递模拟对象,例如构造方法或方法参数。
  4. 如果你的需求中需要维护状态信息,则单例比静态类更适合,因为后者在维护状态信息方面是非常可怕的,并导致狡滑的bug。
  5. 如果是一个非常重的对象,单例可以懒加载,但是静态类没有这样的优势,并且非常热切的加载。
  6. 许多依赖注入的框架对单例都有良好的管理,例如Spring,使用它们非常容易。

线程不安全的单例

懒汉模式

懒汉模式是在程序调用的时候再去初始化实例,不会浪费资源但是存在线程安全问题。


/**
 * 懒汉模式
 */
public class LazyMan {
    private LazyMan() {
    }

    private static LazyMan instance = null;

    public static LazyMan getInstance() {
        if (instance == null) instance = new LazyMan();
        return instance;
    }
}

线程安全的单例

饿汉模式

饿汉模式是在类加载的时候把instance就初始化好,不存在运行时初始化实例的问题,所以是线程安全的。
饿汉模式的缺点在于,如果这个单例没有被使用过,也会在内存中初始化好,所以可能会造成资源的浪费。

/**
 * 饿汉模式
 */
public class HungerMan {

    private static HungerMan instance = new HungerMan();

    private HungerMan() {
    }

    public static HungerMan getInstance() {
        return instance;
    }
}

静态内部类

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

detail:https://blog.csdn.net/mnb65482/article/details/80458571


/**
 * 静态内部类
 */
public class InnerClassSingle {
    private InnerClassSingle() {
    }

    public InnerClassSingle getInstance() {
        return InnerClass.instance;
    }

    public static class InnerClass {
        private static final InnerClassSingle instance = new InnerClassSingle();
    }
}

DCL

双重检测的单例一定要用volatile来修饰instance。

instance = new DlcSingleton();

这个步骤在jvm中分为三步。
1.在堆内存开辟内存空间。
2.在堆内存中实例化SingleTon里面的各个参数。
3.把对象指向堆内存空间。

由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。

枚举单例

反射破坏单例

通过反射可以破坏以上单例

Constructor<DlcSingleton> constructor = DlcSingleton.class.getDeclaredConstructor(null);
constructor.setAccessible(true);//通过setAccessible,将私有属性可见
DlcSingleton singleton = constructor.newInstance();

进入newInstance方法,可以看到:

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");

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

所以枚举的单例可以放置反射的破坏。


public enum EnumSington {
    Instance;

    public void fun() {
        System.out.println("function");
    }

}

posted @ 2020-08-21 17:15  刃牙  阅读(86)  评论(0编辑  收藏  举报