单例模式(设计模式)

目录

静态变量方式(饿汉式)

静态代码块方式(饿汉式)

枚举方式(饿汉式)

线程不安全(懒汉式)

线程安全(懒汉式)

双重检查锁方式

静态内部类方式

单例模式存在的问题

源码分析:

枚举方式不会被破坏

单例设计模式分类两种:

饿汉式:类加载就会导致该单实例对象被创建(三种)。

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建(四种)。

静态变量方式(饿汉式)

public class Singleton {
    //提供私有构造方法
    private Singleton(){}
    //提供静态成员变量
    private static Singleton singleton = new Singleton();
    //提供对外开放方法
    public static Singleton getInstance(){
        return singleton;
    }
}

静态代码块方式(饿汉式)

public class Singleton {
    //提供私有构造方法
    private Singleton(){}
    //提供静态成员变量
    private static Singleton singleton;
    //静态代码块
    static {
        singleton = new Singleton();
    }
    //提供对外开放方法
    public static Singleton getInstance(){
        return singleton;
    }
}

枚举方式(饿汉式)

线程安全,唯一 一个不会被破坏的单例模式实现。

在用反射获得构造器后,使用newInstance创建对象会判断是否为枚举,是枚举报错。

//判断是否是枚举,如果是枚举的话,报、抛出异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    //抛出异常,不能通过反射创建枚举
    throw new IllegalArgumentException
    ("Cannot reflectively create enum objects");
public enum Singleton {
    SINGLETON;
}

线程不安全(懒汉式)

public class Singleton {
    //提供私有构造方法
    private Singleton(){}
    //提供静态成员变量
    private static Singleton singleton;
    //提供对外开放方法
    public static Singleton getInstance(){
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

线程安全(懒汉式)

加了synchronized锁。

public class Singleton {
    //提供私有构造方法
    private Singleton(){}
    //提供静态成员变量
    private static Singleton singleton;
    //提供对外开放方法
    public static synchronized Singleton getInstance(){
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

双重检查锁方式

        双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

        要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

public class Singleton {
    //私有构造方法
    private Singleton() {}
    private static volatile Singleton instance;
   //对外提供静态方法获取该对象
    public static Singleton getInstance() {
		//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
        if(instance == null) {
            synchronized (Singleton.class) {
                //抢到锁之后再次判断是否为空
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

静态内部类方式

        静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

public class Singleton {
    //私有构造方法
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

        静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

单例模式存在的问题

  • 序列化和反射会破坏单例(除了枚举类型)。

        序列化底层也是反射,

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

解决办法:添加readResolve()方法,在反序列化时被反射调用

静态内部类方式

public class Singleton implements Serializable {

    //私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    /**
     * 下面是为了解决序列化反序列化破解单例模式
     */
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

源码分析:

ObjectInputStream类

public final Object readObject() throws IOException, ClassNotFoundException{
    ...
    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(false);//重点查看readObject0方法
    .....
}
    
private Object readObject0(boolean unshared) throws IOException {
    ...
    try {
        switch (tc) {
            ...
            case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
            ...
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }    
}
    
private Object readOrdinaryObject(boolean unshared) throws IOException {
    ...
    //isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
    obj = desc.isInstantiable() ? desc.newInstance() : null; 
    ...
    // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
        // 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
        // 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
        Object rep = desc.invokeReadResolve(obj);
        ...
    }
    return obj;
}

  • 反射破坏单例解决办法:
public class Singleton {

    //私有构造方法
    private Singleton() {
        /*
           反射破解单例模式需要添加的代码
        */
        if(instance != null) {
            throw new RuntimeException();
        }
    }
    
    private static volatile Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

        这种方式比较好理解。当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。

枚举方式不会被破坏

  • 序列化与反序列化时
private Enum readEnum(boolean unshared) throws IOException {
    ......
    //读readOrdinaryObject这块会创建单例,读readEnum不会
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);
    }
  }
}

        枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性。

  • 反射
public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        ......

        //这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常
        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;
    }

        源码很了然,确实无法使用反射创建枚举实例,也就是说明了创建枚举实例只有编译器能够做到而已。

应用

BeanFactory中bean后处理器的排序的比较器对象。

	public static final AnnotationAwareOrderComparator INSTANCE = 
            new AnnotationAwareOrderComparator();

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