初识单例模式【Android】

单例模式是编程经常会用到设计模式,什么是单例模式就不多说啦,但是单例模式使用不当就要尴尬了;

先看看一下我最早菜鸡式的单例模式:

public class Singleton {

    public Singleton() {
    }

    public static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
};

有一点编程经验的人一看就知道又是菜鸡在给自己挖坑啦;

  1. public 修饰 静态变量 instance 和 构造器, 这就给留下了几个严重的问题:1):外部类直接调用常量 instance 作为实例使用出错的bug;2):外部类完全可以通过new对象的方式获取实例,更不用说Java反射MagicPower完全违背了单例设计原则确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例;
  2. 虽然有synchronize 关键字,但高并发的情况下并不是线程安全的,任然有可能发生指令重排带来是线程不安全问题

对指令重排有不解的小伙伴可参考:java volatile关键字解惑

对症下药,经过改造后双重锁校验机制单例:

public class Singleton {

    private volatile static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

修改:

  1. volatile 关键字修饰静态常量instance避免高并发下发生指令重排导致创建多个实例问题;
  2. private 关键字修饰的构造器避免外部类通过new对象方法实例对象;

存在的问题

  1. 关键字synchronizedvolatile带来的性能消耗问题;
  2. 说好的反射获取实例的问题;

先说问题1 另一个解决思路吧,那就是静态内部类单例模式了:

public class Singleton {

    private Singleton() {
    }

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

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

静态内部类:

    在Singleton类加载时,内部静态类SingletonHolder并没有加载,只用调用getInstance()时才会执行加载SingletonHolder类中同时通过new Singleon()将实例化Singleton对象赋值给静态常量INSTANCE,,借助Javastatic 常量属性(JVM机制)保证了Singleton的单例(线程安全),同时又避免了Java锁造成性能问题;但是Singleton类中有构造器,给强大的反射留下了可趁之机,所以理论上还是存在出现多个实例对象的现象;

其实静态内部类也是属于懒汉单例的变种,当需要的时候再去加载;

other:

扫描二维码关注公众号,回复: 1769939 查看本文章

最近看到一个“奇葩”的单例模式解决序列化存储数据,读出数据反序列引发的单例类多次实例化问题:具体描述和代码见分割线中

----------------------------------------------------分割线-------------------------------------------------------

不知道大家有没有想到过一个问题,那就是序列化。我们可以通过以下代码将实例写入磁盘,然后再从磁盘读出,即使构造方法是私有的,反序列化也是可以通过特殊的途径去重新创建一个新的实例,代码如下:

public Singleton createNewInstance() throws IOException, ClassNotFoundException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    // 此处的singleton为通过单例模式获取到的实例对象
    oos.writeObject(singleton);

    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bais);
    // 此时返回一个反序列化后得到的新的实例对象
    return (Singleton) ois.readObject();
}
可以通过上面的代码看到,反序列化后可以得到一个新的实例对象,那么这种现象没法避免了吗?其实是可以避免的。反序列化提供了一个很特别的方法,即一个私有、被实例化的方法readResolve(),这个方法可以让开发人员控制对象的反序列化。想要杜绝上面现象的发生,那么就可以在单例模式中加入readResolve()方法,代码如下:

private Object readResolve() {
    // 此处返回单例模式中的实例对象
    return sInstance;
}

--------------------------------------------------分割线-------------------------------------------------------

分割线中内容见:Java设计模式——单例模式(Singleton pattern)

看到上面代码也是耳目一新,看代码解决多次实例化应该是没问题的[我是没有验证过];我感到不解的是一般是JavaBean数据类才会序列化存储,但JavaBean好像很少见到单例的JavaBean[我是菜鸡,哪位大佬帮忙解答一下],于是我在评论中请教大佬,大佬的回复让我瞬间“顿悟”:


有兴趣的小伙伴可以自己run一下;


以上几个单例都存在一个问题就是反射创建多个实例对象,下面就是利用Java枚举enum实现的单例

public enum SingltenEnum {
    
    INSTANCE;
    
    public void utils() {
        //utils 具体逻辑
    }
}

枚举单例:

    在需要调用的utils工具是只需要:SingltenEnum.INSTANCE.utils();  即可调用utils 方法;

     枚举单例和静态内部类都是巧妙的利用的JVM机制实现单例,同时枚举类单例更是

借助枚举特征反射这个神器也拦截在外,实现了正真的单例;但是这个纯正的单例也有JavaAPI普遍的特征就是有点 臃肿执行效率会有所打折,所以个人基本上是使用更轻便的 静态内部类实现单例的。移动端开发中代码逻辑相对比较容易,个人认为可以在写代码过程中去花苦力避免反射创建实例,比牺牲性能使用沉重的代码更划算;

结束语:

    看到这里估计早就有人会说你这些都是少了恶汉模式呢,首先饿汉单例其实要比懒汉单例要容易一些,其次我个人认为恶汉模式预加载带来会过早的霸占内存空间,完全可以用懒汉替代的;在移动端的开发中:首要的与用户交互的界面一定要的有非常舒适的体验,内在的功能再丰富逻辑再复杂也不能违背这一原则,毕竟80%用户只是使用20%功能;这就是需要在移动端做大量的优化逻辑代码的工作提升APP的舒适度,在移动端有限的CPU、GPU、RAM的提前下,尽可能的提升代码的执行效率,优化代码逻辑就是根本所在了。所以更建议使用高效静态内部类来实现单例,移动端开发人员就用苦力避免反射更合适;

由于个人水平有限认知不足,如有纰漏、错误望诸位大佬批评指正!

参考资料

漫画:如何写出更优雅的单例模式?

Java 枚举类的基本使用

Java设计模式——单例模式(Singleton pattern)


猜你喜欢

转载自blog.csdn.net/black_bird_cn/article/details/78925212