设计模式 -- 单例模式

一:为什么使用

对象频繁创建、销毁对系统性能和内存来说不是一个好事情,特别是及其消耗资源的大对象或是对象实例化需要进行I/O操作。使用单例模式在内存中确保对象唯一,可以很好解决上述问题

二:饿汉式单例

public class  Singleton {

    private Singleton(){}

    private static Singleton sf = new Singleton();
    
    public static Singleton getInstance(){
        return sf;
    }

}

饿汉式特点:

  1. 私有构造函数阻止new实例化对象操作确保对象唯一
  2. 提供静态方法返回对象

饿汉式问题:

  1. 静态属性sf会跟随类加载完成对象实例初始化,但是如果这个对象暂时是不需要的,是否可以延时加载即使用再初始化

三:懒汉式单例

public class Singleton {

    private Singleton(){}

    private static Singleton sf = null;

    public static Singleton getInstance(){
        if (sf == null)
            return sf = new Singleton();
        return sf;
    }

}

懒汉式特点:

  1. 通过在方法中判断、创建对象的方式实现了延时加载的目标

懒汉式问题:

  1. if条件判断的使用导致并发场景下对象不唯一情况产生,即线程A通过判定在初始化之前线程B获得资源完成初始化,这时候A再获取资源进行初始化操作

四:双重锁校验

直接在getInstance()上加synchronized关键字确实可以解决线程安全问题,但是锁是及其消耗资源的,为了性能最优可以减少锁资源争夺从而提出双重锁校验

public class Singleton {

    private Singleton(){}

    private static volatile Singleton sf = null;
    
    private static final Object o = new Object();

    public static Singleton getInstance(){
        if (sf == null) {
            synchronized (o) {
                if (sf == null)
                    return sf = new Singleton();
            }
        }
        return sf;
    }

}

务必注意sf对象使用关键字volatile修饰保证线程间数据透明,线程可以及时获取到sf对象的变化信息

五:内部类实现

双重锁校验在一定程度上优化了并发线程对于锁资源的争夺,但是还是无法完全避免。利用内部类实现则可以从根上改变使用锁解决线程安全问题

public class Singleton {

    private Singleton(){}

    private static class InnerClass{
        private static Singleton sf = new Singleton();
    }
    
    public static Singleton getInstance(){
        return InnerClass.sf;
    }
}

六:序列化、反射安全

上面四个单例模式方案逐步递进解决有关延迟加载与线程安全等,但是都共同存在反序列化破坏单例规则即反序列化产生不唯一对象的问题

public class Singleton implements Serializable{

    private Singleton(){}

    private static Singleton s = new Singleton();

    public static Singleton getInstance(){
        return s;
    }

    public static void write() throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.txt"));
        oos.writeObject(Singleton.getInstance());
        oos.flush();
        oos.close();
    }

    public static Singleton get() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.txt"));
        Singleton s = (Singleton)ois.readObject();
        return s;
    }
    

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
        Singleton singleton = get();
        System.out.println(singleton == s); // false
    }
}

解决序列化带来的对象不唯一问题比较简单粗暴,如下所示声明方法readResolve()即可:

    private Object readResolve(){
        return s;
    }

反射对单例模式的破坏同样很直接,针对反射问题可以对构造函数进行如下处理。对单例模式的类使用反射本身就是在犯罪,虽然如下处理可以防止,但是要铁了心修改flag的值这也是无法防范

    private static boolean flag = true; 
    private Singleton(){
        if(flag)
            flag = false;
        else
            throw  new RuntimeException("this Object already exists!");
    }
    原文作者:周仕林
    原文地址: https://blog.csdn.net/weixin_43495590/article/details/89022853
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞