Head First Design Mode(6)-单件模式

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 

单件模式:    

    独一无二的对象;

    单件模式(Singleton Pattern):用来创建独一无二的,只能有一个实例的对象的入场券;

    单件模式的类图可以说是最简单的,只有一个类,尽管从类设计的视角来说他很简单,但是实现上还是会遇到不少问题;

 

一些只需要一个的对象:

    比如 线程池 缓存 对话框 日志对象等,很大的可能是这些类只能有一个实例,如果制造出多个实例,就会导致很多问题;

    使用单件模式实现类似的需求,可以确保只有一个实例会被创建,单件模式会提供一个全局的访问点,和全局变量一样方便,有没有全局变量的缺点;

    比如将对象赋值给一个全局变量,就必须在程序一开始就创建好对象;但如果这是一个非常消耗资源的过程,而程序又迟迟用不到这个对象,就会形成浪费,最好在需要时才创建对象;

 

建立一个单件模式:

    将构造器私有,只有类的内部可以使用;

    MyClass1有一个静态方法,就是我们熟悉的类方法,使用类名调用;

    在静态方法中调用私有的构造器;

Head First Design Mode(6)-单件模式

 

剖析经典的单件模式实现:

    利用一个静态变量来记录Singleton类的唯一实例;

        uniqueInstance一开始并没有创建,当需要的时候才会产生,这就是“延迟实例化”(lazy instantiaze);

    构造器声明为私有,只有Singleton类内才可以调用该构造器;

    使用getInstance()方法实例化对象,并返回这个实例;

    当然,Singleton也是一个正常的类,可以具有其他用途的实例变量和方法;

Head First Design Mode(6)-单件模式

 

单件模式构建对象:

    让任何时刻只有一个对象,确保程序中使用的全局资源只有一份;

    常被用来管理共享的资源,如数据库连接或线程池;

    由于构造器是私有的,任何人想获取实例,就需要请求得到,而不可以自行实例化得到;

 

定义单件模式:

    单件模式确保一个类只有一个实例,并提交一个全局访问点;

 

想要取得单件实例,通过单件类是唯一途径;当需要实例时,向类查询,它会返回单个实例;

 

类图:

Head First Design Mode(6)-单件模式

 

这个类针对没问题吗——比如在多线程的环境中;

    当JVM使用两个线程运行单例实例化方法时,可能都通过了uniqueInstance == null的判断,这将导致静态变量被初始化两次,在一个对象初始化返回之后,另一个变量有被初始化付给了uniqueInstance并返回了,最终两个线程返回了两个不同的实例对象;

    

处理多线程:

    只要把getInstance()变成同步(synchronized)方法,多线程的问题就可以解决了;

    关键字synchronized修饰的方法,JVM会迫使每个线程进入这个方法之前,需要先等候别的线程离开该方法,即两个线程不会同时进入这个方法;

Head First Design Mode(6)-单件模式

 

使用synchronized修饰getInstance()方法的问题:

    只有在第一次执行此方法时,才真正需要同步;一旦设置了uniqueInstance变量,就不再需要同步这个方法了;

    之后每次调用这个方法,同步都是一种累赘;

 

改善多线程:

    为了符合多数Java程序,我们需要确保单件模式能在多线程下正常工作;

    显然只使用同步getInstance()的做法并不行,当然如果对性能要求不高,那就什么也别做了,这样刚好;

 

“急切”创建实例:

    如果我们的应用程序在创建和运行方便负担不重,可以考虑在创建静态变量uniqueInstance的同时对其进行初始化操作,这样就可以保证线程安全了;

    利用这种做法,JVM会在加载类的时候马上创建此唯一的单件实例,先于任何线程访问;

Head First Design Mode(6)-单件模式

 

双重检查加锁:

    用“双重检查加锁”(double-checked locking),在getInstance()中减少使用同步;

    首先检查实例是否已经创建,未创建才进行同步(同步区块);这样就只有第一次会同步;

    这个做法会大大减少getInstance()的时间消耗;

Head First Design Mode(6)-单件模式

 

volatile关键字:

    在 JDK1. 5 之后,volatile确保党uniqueInstance变量被初始化成Singleton实例时,多个线程正确的处理,保证 sInstance 对象每次都是从主内存中读取;

    “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”  

    

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:  

    1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;  

    2)它会强制将对缓存的修改操作立即写入主存;  

    3)如果是写操作,它会导致其他CPU中对应的缓存行无效。 

 

    synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。

 

是否可以创建一个类把所有的方法都定义成静态的,把类当成一个单件?

    静态初始化的控制权在Java手上,多个静态变量的初始化顺序如果有要求就会出现问题;

 

类加载器的问题:

    两个类加载器具有不同命名空间,不同的类加载器加载同一个类的话,确实会被加载多次;

    这种情况下的单件请自行指定同一类加载器,小心使用;

 

单件类是否可继承:

    如果想要继承单件类,就需要把构造器公开;那就不是一个真正的单件了;

    而且,子类会和父类共享静态的实例变量,这可能不是你想要的;

 

相比单件模式,全局变量也可以提供全局访问,但是不能确保只有一个实例;多个全局变量还有可能造成命名空间的污染;

当然单例模式也有可能被滥用;通常使用单件模式的机会不多;

 

总结:

1.单件模式确保程序中一个类最多只有一个实例;

2.单件模式也提供访问这个实例的全局点;

3.在Java中实现单例模式需要私有的构造器、一个静态方法和一个静态变量;

4.确定在性能和资源上的限制,小心选择方案实现单件,已解决多线程问题;

5.JDK1.5之后,双重检查加锁实现才有效;

6.小心,使用多个类加载器,可能会导致单件失效而产生多个实例;

 

OO基础:

    抽象;

    封装

    继承;

    多态;

OO原则:

    封装变化

    多用组合,少用继承

    针对接口编程,不针对实现编程

    为交互对象之间的松耦合设计而努力;

    类应该对扩展开放,对修改关闭;

    依赖抽象,不要依赖具体类;

OO模式:

    策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;

    观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;

    装饰者模式:动态地将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择;

    简单工厂模式:

        工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;

        抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确具体的类;

    ——单件模式:确保一个类只有一个实例,并提供全局访问点;

 

思考:

OC及Swift的单例模式如何实现?