继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Head First设计模式读书总结——单件模式

尕小刘
关注TA
已关注
手记 12
粉丝 26
获赞 228

用来创建独一无二的,只能有一个实例的对象的入场券。
单件模式的类图可以说是所有模式的类图中最简单的,事实上,它的类图只有一个类。可是也不是那么简单的。
有一些对象其实我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象、充当打印机、显卡等设备的驱动程序的对象,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,比如:程序的行为异常、资源使用过量,或者是不一致的结果。
但是难道不能靠程序员之间的约定或是利用全局变量做到?你知道的,利用Java的静态变量就可以做到。
但是有更好的做法,大家应该到乐意接受,单件模式是经得起时间考验的方法,可以确保只有一个实例会被创建,单件模式也给了我们一个全局访问点,和全局变量一样方便,又没有全局变量的缺点。
全局变量的缺点:如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象,万一这个对象非常耗费资源,而程序在这次的执行过程中又一直没用到它,不就是浪费了吗?
问:如何创建一个对象?
答:new MyObject();
问:万一另一个对象想创建MyObject会怎样?可以再次new MyObject吗?
答:是的,当然可以。
问:所以,一旦有一个类,我们是否都能多次地实例化它?
答:如果是公开的类,就可以。
问:如果不是的话,会怎样?
答:如果不是公开尅,只有同一个包内的类可以实例化它,但是仍可以实例化多次。
问:你知道可以这么做吗?
class MyClass{ private MyClass(){} }
答:我认为含有私有的构造器的类不能被实例化。
问:你认为这样如何?
class MyClass{ public static MyClass getInstance(){} }
答:MyClass有一个静态方法,我们可以这样调用这个方法:MyClass.getInstance();
问:假如把这些合在一起"是否"就可以初始化一个MyClass?
class MyClass{ private MyClass(){} public static MyClass getInstance(){ return new MyClass(); } }
剖析经典的单件模式实现

class Singleton{
    private static Singleton singleton;
    private  Singleton(){}
    public static Singleton getInstance(){
        if(singleton==null){
            singleton=new Singleton();
        }
        return singleton;
    }
}

在后面,你会看到这个版本有一些问题。
定义单件模式
单件模式确保一个类只有一个实例,并提供一个全局访问点。

单件模式类图:
单件模式类图
书中是有一个列子说明多线程的问题但是太繁琐,这里简单总结下。
上面的例子中如果在判断实例是否为空的那一步有另一个线程进入,就会造成都判断出那个实例都是为空的。那么在另一个线程中也会重复创建新的实例。
处理多线程
只要把getInstance()变成同步{synchronized}方法,多线程灾难几乎就可以轻易地解决了:

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

这样是可以的,凡是同步会降低性能,这又是另一个问题。
而且,只有第一次执行此方法时,才真正需要同步,换句话说,一旦设置好singleton变量,就不再需要同步这个方法了,之后每次调用这个方法,同步都是一种累赘。
能够改善多线程吗?
1.如果getInstance()的性能对应用程序不是很关键,就什么都别做。
如果你的程序可以接受getInstance()造成的额外负担,就忘了这件事吧,但是你必须知道,同步一个方法可能造成程序执行效率下降100倍。因此,如果将getInstance()的程序使用在频繁运行的地方,你可能就得重新考虑了。
2.使用"急切"创建实例,而不用延迟实例化的做法
如果应用程序总是创建并使用单间实例,或者在创建和运行时方面的负担不太繁重,你可能想要哦急切创建此单件。如下:

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

利用这个做法,我们依赖jvm在加载这个类时马上创建此唯一的单件实例,JVM保证在任何线程访问singleton静态变量之前,一定先创建此实例。
3用“双重检查加锁”,在 getInstance()中减少使用同步
利用双重检查加锁,首先检查是否实例已经创建了,如果尚未创建,“才”进行同步,这样一来,只有第一次会同步,这正是我们想要的。

class Singleton{
    //volatile关键词确保,当singleton变量被初始化成Singleton实例时,多个线程正确处理singleton变量
    private volatile  static Singleton singleton;
    private  Singleton(){}
    public static  Singleton getInstance(){
        //如果不存在,就进入同步区块
        if (singleton==null){
            //只有第一次才彻底执行这里的代码
            synchronized (Singleton.class){
                if(singleton==null){
                    singleton=new Singleton();
                }
            }
        }
        return singleton;
    }
}

问:难道我不能创建一个类,把所有的方法和变量都定义为静态的,把类直接当做一个单件?
答:如果你的类自给自足,而且不依赖于负责的初始化,那么你可以这么做,但是,因为静态初始化的控制权实在Java手上,这么做有可能导致混乱,特别是当有许多类牵涉其中的时候,这么做常常会造成一些微妙的,不容易发现的和初始化的次序有关的Bug,除非你有绝对的必要使用类的单件,否则还是建议使用对象的单件,比较保险。

问:那么类加载器(classloader)呢?听说两个类加载器可能有机会各自创建自己的单件实例。
答:是的,每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次,如果这样的事情发生在单件上,就会产生多个单件并存在怪异现象,所以,如果你的程序有多个类加载器又同时使用了单件模式,请小心,有一个解决办法:自行制定类加载器, 并制定同一个类加载器。

问:我想把单件类当成超类,设计出子类,但是我遇到了问题:究竟可不可以继承单件类?
答:继承单件类会遇到一个问题,构造器是私有的,到时候就必须把构造器改成公开的或受保护的,但是这么一来就不算是“真正的”单件了,别的类可以实例化它。
如果你真把构造器的访问权限改了,还有另一个问题会出现,单件的实现是利用静态变量,直接继承会导致所有的派生类共享同一个实例变量,这可能不是你想要的,所以,想要让子类能工作顺利,基类必须事项注册表功能。
在这么做之前,你得想想,继承单件能带来什么好处,还有通常适合使用单件模式的机会不多。

问:我还是不了解为何全局变量比单件模式差。
答:在Java中,全局变量基本上就是对对象的静态引用,在这样的情况下使用全局变量会有一些缺点,我们已经提到了其中的一个:急切实例化VS.延迟实例化。但是我们要记住这个模式的目的:确保类只有一个实例并提供全局访问。全局变量可以提供全局访问,但是不能确保只有一个实例。全局变量也会变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间的污染。
单件不鼓励这样的现象,但单件任然可能被滥用。

打开App,阅读手记
2人推荐
发表评论
随时随地看视频慕课网APP