设计模式---单例模式

字数 1153阅读 49
1.1 单例模式介绍

单例模式可能是应用最广,最熟悉的设计模式了。这种设计模式可以确保某个类只有一个实例,并且自行实例化并向整个系统提供这个实例。通常在创建一个对象花费很大考虑使用。

1.2 UML类图
image.png

单例模式有如下几个关键点:
1)构造函数不对外开放,一般为private
2)通过一个静态方法或者枚举返回单例类对象
3)确保单例类的对象有且只有一个,尤其是在多线程模式下
4)确保单例模式在反序列化的时候不会重新构建对象
尤其是后两点,让很多经常使用的单例模式写法产生了很大的问题。

1.3单例模式示例
  1. 饿汉式:在声明静态对象的时候就已经初始化。这种模式比较简单,直接见代码:
public class Singleton {
      private static final Singleton mSingleton = new Singleton();
      
      private Singleton(){
      }

      public Singleton getInstance(){
            return mSingleton;
      }
}
  1. 懒汉式:声明一个静态对象,并且在用户第一调用getInstance()方法是进行初始化,相比于饿汉式,延迟了初始化,这样可以降低系统首次启动的资源占用。
public class Singleton {
      private static final Singleton mSingleton;
      
      private Singleton(){
      }

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

但是这种方式也有问题,每次调用获取单例对象的方法都会进行线程同步判断,造成不必要的同步开销,所以不推荐这种方式。

  1. Double Check Lock(DCL实例单例)
    DCL就是用来解决饿汉式线程同步消耗的问题的。
public class Singleton {
      private volatile static final Singleton mSingleton;
      
      private Singleton(){
      }

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

其实对于饿汉式最好的方法就是自己多写几遍就能感觉到这样做的好处了。不需要重复的线程判断,而且也能保证线程安全。这种方法看起来已经能够很好地满足需求了,但是在《Java并发编程实践》中明确的指出这样的方法是丑陋的,不建议使用的。并且建议用更好的静态内部类单例模式来代替。

  1. 静态内部类单例模式
    直接上代码:
public class Singleton {
      private static final Singleton mSingleton;
      
      private Singleton(){
      }

      public Singleton getInstance(){
            return SingletonHolder.sInstance;
      }

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

这种方法当Singleton第一次加载的时候并不会初始化sInstance,只有在第一次调用Singleton的getInstance()的时候才会导致sInstance初始化。而且这种方法也能够保证线程安全。当然在我看来最大的意义可能是又会少写几行代码~~~

  1. 枚举单例
    想不想要一种比静态内部类单例模式写法更简单的单例实现方式呢?见代码:
public enum SingletonEnum{
        INSTANCE;
        public void doSomething(){
                ....
        }
}

由于枚举类型的创建本身是线程安全的,所以这样写完全没有问题,而且在任何情况下它都是安全的。什么意思?看看上面的关键点 4)确保单例模式在反序列化的时候不会重新构建对象,这就尴尬了,如果用1-4的方法来写单例模式,要在序列化和反序列化后仍然保持同一个对象,就必须加入readResolve()方法,来人为的控制序列化和反序列化,这样又会带来反序列化的种种问题。非常繁琐。不如直接使用枚举单例来的方便。

  1. 使用容器实现单例
    听着挺玄乎,其实就是把类保存在静态HashMap中。看代码:
public class SingletonManager{
      private static Map<String,Object> map = new HashMap<>();

      private SingletonManager(){
      }

       public static void add(){
            ....
       }

        public static Object get(){
                ...
        }
}

这样做的好处是我们可以同时管理多个单例,而且由于类型是Object,所以能够很好地隐藏具体的实现,降低耦合。

以上就介绍完了单例模式的几种实现方式,单纯的使用单例,推荐枚举单例的做法,如果需要管理多个单例,容器实现单例能很好地满足需求。

单例模式优点在于减少类的实例化数量,降低开销。而且可以全局访问,这对保存一些全局变量有很大帮助。

但是单例模式也有很大的缺点---扩展困难,如果想要扩展功能,除了修改代码外没有很好地方法,这样就和开闭原则(OCP)相悖。而且单例如果持有Android中的Context(比如Activity Context)极易引起内存泄漏,如果一定要传递Context,Application Context是个很好的选择。

推荐阅读更多精彩内容