《深入浅出设计模式》读书笔记 - 第五章  单例模式(Singleton)

目录
体会1:static不能称霸全宇宙。
体会2:逻辑上单一的对象应该应用单例模式。
体会3:多线程与单例。
体会4:单例串行化/串流化的关系。
体会5:单例模式子类化的问题。
体会1:static不能称霸全宇宙。

很多人以为,static无敌。错的,static有时无敌,什么时候呢?如果是public static final N CONSTANT=xxx;那么这是非常好的一种代码风格,因为这代表了一系列该类别通用的常数。这里的N除了几种“原生类型”(primitive type),例如int,char等之外,还包括了“不可变类别”(immutable class),例如String。在上述两种情况之外,都尽量要避免这种乱用static的风格。例如public static MyObject myObject;这种使用破坏了类的封装性。

此外,static方法的使用也很有讲究,只有在实现“纯工具类”(例如Math类)或者实现“简单工厂”逻辑的时候,可以容忍public static方法的存在。如果不分析具体情况,乱用共有静态方法,则退回到了面向过程的老路,为什么呢?static方法所处的静态环境没有办法存取实例变量,这就逼着我们把所有本来应该为实例变量的域声明为静态域,整个一个类退化为一个“具有若干成员和函数指针的结构体(struct)”,不是吗?

体会2:逻辑上单一的对象应该应用单例模式。

注册表、设备驱动程序、线程池、窗口管理器等等这些概念,如果我们允许多个实例存在,必然在整个系统之间引发混乱,所以应该引入单例模式。这一点暂时不多说,以后补充。
《深入浅出设计模式》读书笔记 - 第五章  单例模式(Singleton)_第1张图片
体会3:多线程与单例。

在多线程环境下应用单例,有以下几种方法:

1.将整个getInstance方法设定为同步函数,并在其中进行“惰性初始化”。如果构建该对象的花销远远大于获取同步锁的花销,那么此种方式非常值得。



2.在声明完单例引用之后立即实例化。如果构建该对象的花销远远小于获取同步锁的花销,那么此种方式非常值得。



3.“双重检测锁”模式。如182页所示,这种看似“聪明”的方式,其实有着巨大的漏洞。简单的说,在1.5之前的JVM中,代码会进行“重整”,单例引用uniqueInstance有时尽管不为null,但是此时所引用的那个“单例对象”,并没有被完全初始化。也就是new Singleton()函数未正式完成其工作之前,JVM可以根据Java规范,重整代码,使得uniqueInstance先获得这个“单例对象”的引用,这样一来,第二个线程直接判定单例已完成实例化,故接下来的客户代码会直接使用单例对象的数据,但是有些数据并没有被正确的初始化,因为new Singleton()尚未正式完成。

《深入浅出设计模式》读书笔记 - 第五章  单例模式(Singleton)_第2张图片

我是泛泛而谈,具体的信息请参阅这篇文章:
The “Double-Checked Locking is Broken” Declaration

4.既想“惰性初始化”,又想避免“获取同步锁开销”的方法。大名鼎鼎的Joshua Bloch在神作《Effective Java》中,建议1.5及以后的版本用一个“含有单一枚举值的enum来实现单例”,并举例如下。

      // Enum singleton - the preferred approach
      public enum Elvis {
        INSTANCE;
        public void leaveTheBuilding() { ... }
      }
    
这就是巧妙地运用了Java规范中关于enum初始化的特性。

在1.5之前,Bloch推荐的方法是用一个“辅助占位类”来包容这个实例,如下:

      // Lazy initialization holder class idiom for static fields
      private static class FieldHolder {
        static final FieldType field = computeFieldValue();
      }
      static FieldType getField() { return FieldHolder.field; }
    
同样也是运用了Java规范中关于类加载的相关知识。

体会4:单例串行化/串流化的关系。

这个问题比较复杂,我暂时还没有研究,但是提醒大家,要记得重写读取解析函数,再引用一次Bloch的代码:

      // readResolve method to preserve singleton property
      private Object readResolve() {
        // Return the one true Elvis and let the garbage collector
        // take care of the Elvis impersonator.
        return INSTANCE;
      }
    
这个问题将来我会单独讲解,这里先放一放吧。

体会5:单例模式子类化的问题。

有时我们可能要对某单例类别子类化,例如“窗口管理器”应该是一个单例,但是如果这是一个抽象类,有不同的子类去实现,每次系统启动的时候,有且只有一个子类窗口管理器被实例化,这就需要处理单例模式和子类化之间的问题了。

问题在于,单例类的构造函数是private,为了能被子类化,至少应该是“包缺省”级别的,但这样一来,就没办法控制构造函数的使用了。看了本书和《设计模式》的讨论,都说在基类设置一个注册表的机制。本人认为这样做不一定最好。我采取的做法如下:

      public abstract class MySingleton{
        private static MySingleton instance;
        
        MySingleton(){
          if (instance==null){
            instance=this;
          }else{
            throw new IllegalStateException(
                               "There is already a singleton instantiated: "+instance);
          }
        }
        
        public  static  MySingleton getInstance(){
          return instance;
        }
      }
      
      public final class SubSingleton extends MySingleton{
      }
    
这样我们把第一次初始化单例的任务交给一个“简单工厂”。例如在系统启动的时候,调用singletonFactory.createSingleton(),此后,我们就可以通过MySingleton.getInstance()来获得正确的单例对象引用了。下次系统启动的时候,单例工厂会再去初始化正确的单例。

转载于:https://www.cnblogs.com/kamome/archive/2010/02/02/1661605.html

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