大话西游之设计模式_从猴王出世看singleton

猴王出世

盘古开天辟地之后,天下分为四大部洲,在东胜神州傲来国的海边上有一个花果山,此山风景秀丽,美不胜收,又多桃树,遂成了猕猴的天堂。
花果山上有一块仙石,此石自天地开始便吸收天地灵气,慢慢周身生成360窍,开始呼吸,好像婴儿一样。终于有一天能量充满,从石头里蹦出来个灵胎,见风化为一个石猴,出来之后,眼里冒出两道神光,先拜天地,再拜四方,惊动的天上的玉皇大帝,之后石猴喝了人间的河水,把灵气压住,成了一个普通的石猴。石猴性格洒脱、好动、很快和猕猴们打成一片,吃饭、玩耍、睡觉都在一起,过着快快乐乐的生活。

故事梗概

有一座山,山上面有一块仙石,仙头里面有个灵胎,灵胎出世,变成石猴;
山上有很多桃树和猕猴群。
石猴加入到了猕猴群。

代码模拟

由于整个模拟过程,代码较多,请点击 http://download.csdn.net/detail/myhc2014/9194253 下载,代码中更加清晰的了解猴王出世故事的流程及代码模拟,也能清楚的了解到singleton的全部内容,里面还有其他模式哦。

关键代码

爱看西游的人都知道,整个西游中,一共有多少个孙悟空啊?
你的回答可能是这样的:
只有一个,天上地下只有一个……
实际上,大家想表达的是只有一个孙悟空。
大家都知道,在面向对象的语言中,一切都是对象,想生成一个对象,只要通过构造方法new一下即可,
那么,在代码中,如何保证只有一个孙悟空?
我是这么做到的:

public class StoneMonkey extends Lingtai implements IMonkey {

    /**
     * 花果山仙石内的灵胎所变的石猴,天地间只有一个
     */
    private static StoneMonkey stoneMonkey;

    private StoneMonkey(List<LingQi> lingQis) {
        super();
        System.out.println("石猴孕育完成");
    }

    /**
     * 单例
     * 
     * @return
     */
    public static synchronized StoneMonkey getStoneMonkey(List<LingQi> lingQis) {
        if (stoneMonkey == null) {
            stoneMonkey = new StoneMonkey(lingQis);
        }
        return stoneMonkey;
    }

您可能感觉没啥啊,这个看着没什么的代码,就能实现?
这个真的能实现,具体原因听我道来(采用苏格拉底诱导问答)。

构造方法private,外面能new对象吗?不能
外面不能new对象,那么类内部能访问private构造方法吗?能,但是即使创建了对象,外面也无法访问啊
如果通过对象调用的方式确实无法访问,但是别忘了,还有可以通过类来调用的方法哦哦,是的,静态方法的确可以通过类直接进行访问
那么通过静态方法,调用能否解决私有构造方法类对象的创建问题?恩,可以解决的
这样就完了吗?应该可以了
是的,在单线程的应用中是可以了,但在多线程并发下呢?
synchronized/ lock 可以帮到你

好了,通过上面的对话式描述,对上文的代码是不是更清晰了?
其实啊,我用了一个非常简单的模式来实现这个功能——singleton

singleton? 着挺高级的,他到底是个什么东西?
让我们来一步一步的揭开他的神秘面纱

singleton

  1. 试用场景
    当某个事情是独一无二的,整个代码运行环境中,不会在出现第二个对象的时候(如天上地下就一个孙悟空),就可以考虑使用singleton。
  2. 代码特点
    2.1. 构造方法私有
    只有构造方法是私有的情况下,其他类才不能直接调用该类的构造方法来创建对象,此条件是单例的基础。
    2.2 开放静态获取类实例方法
    在没有该类对象的情况下,为了让其他类能正常的获取到该类的对象,所以需要写一个public static 的方法,用于返回该类的对象/实例。
    2.3 私有静态对象实例
    为了保证静态方法能获取到实例对象,所需需要一个静态实例;
    为了保证,外界只能通过开放的静态同步方法获取该实例,所以必须为private。
  3. 标准类图
    后续补上
  4. 标准实现代码
    singleton共有两种写法,饿汉式 和 懒汉式 singleton,懒汉式还可以进化为 Double-checked Locking ,上文中的代码仅仅是懒汉式singleton
    4.1 懒汉式:

        最常用的一种实现方案

public class StoneMonkey extends Lingtai implements IMonkey {

    /**
     * 花果山仙石内的灵胎所变的石猴,天地间只有一个
     */
    private static StoneMonkey stoneMonkey;

    private StoneMonkey(List<LingQi> lingQis) {
        super();
        System.out.println("石猴孕育完成");
    }

    /**
     * 单例
     * 
     * @return
     */
    public static synchronized StoneMonkey getStoneMonkey(List<LingQi> lingQis) {
        if (stoneMonkey == null) {
            stoneMonkey = new StoneMonkey(lingQis);
        }
        return stoneMonkey;
    }

       有些朋友可能会问,为啥在getStoneMonkey前加了一个synchronized
       因为在多线程的应用里,不加 synchronized 可能会出现问题。
       示例如下:
       假设对 getStoneMonkey()方法的两个调用几乎同时发生,这时候会发生什么?
       1. 第一个线程检测实例是否存在,因为实例不存在,该线程执行创建第一个实例的代码部分(new StoneMonkey(lingQis))。
       2. 然而,假设在实例化完成之前,另一个线程也来检测实例成员变量是否为null。因为第一个线程还什么都没有创建,实例成员变量仍然等于null,所以第二个线程也执行了创建一个对象的代码(new StoneMonkey(lingQis))。
       3. 现在,两个线程都执行了 singleton对象的new操作,因此创建了两个对象。
       影响可想而知(出现了多个孙悟空)!
       为了解决这个问题,可以对整个获取实例对象方法加锁(synchronized)。

懒汉式构造对象特点:
在其他类调用“get实例对象方法”(如getStoneMonkey(List lingQis)) 的时候,在创建对象。
优点: 由于在需要的时候,才实例化对象,故在不需要使用对象的时候,无没有必要的内存占用(相对于饿汉式)。
缺点: 由于在需要的时候,才实例化对象,故获“get实例对象方法”耗时长

4.2 Double-checked Locking

        Double-checked Locking 只适用于多线程程序,如果在单线程的情况下,Double-checked Locking 是没有意义的(根本就不需要考虑多线程问题)。
        鉴于懒汉式的创建方法中,synchronized 锁定了整个方法,而其他线程需要获取该对象时,必须等待。这将导致一个瓶颈。
        为了解决这个瓶颈问题,采用双重校验的方式进行相应的解决。

public class StoneMonkey2 extends Lingtai implements IMonkey {

    /**
     * 花果山仙石内的灵胎所变的石猴,天地间只有一个
     */
    private static StoneMonkey2 stoneMonkey;

    private StoneMonkey2(List<LingQi> lingQis) {
        super();
        System.out.println("石猴孕育完成");
    }

    /**
     * 双重检测加锁
     * 
     * @return
     */
    public static StoneMonkey2 getStoneMonkey(List<LingQi> lingQis) {
        if (stoneMonkey == null) {
            synchronized (StoneMonkey2.class) {
                if (stoneMonkey == null) {
                    stoneMonkey = new StoneMonkey2(lingQis);
                }
            }
        }
        return stoneMonkey;
    }

       特点: 在创建对象前,添加一次检查,避免不必要的锁定

4.3 饿汉式

public class StoneMonkey3 extends Lingtai implements IMonkey {

    /**
     * 花果山仙石内的灵胎所变的石猴,天地间只有一个
     */
    private static StoneMonkey3 stoneMonkey = new StoneMonkey3();;

    private StoneMonkey3() {
        super();
        System.out.println("石猴孕育完成,没有吸收灵气");
    }

    /**
     * 饿汉式
     * 
     * @return
     */
    public static StoneMonkey3 getStoneMonkey(List<LingQi> lingQis) {
        return stoneMonkey;
    }

       特点:在类被虚拟机加载时就自动调用构造方法(StoneMonkey3())生成实例对象了。
       优点: 由于类被加载的时候,就创建了实例,故外面获取实例对象时,耗时短。
       缺点:由于类被加载的时候,就创建了实例,故可能会造成无用的内存占用。

总结:

       单例模式是非常非常简单的一个模式,其两种实现方式也是比较简单的,至于平时的使用,建议大家多考虑一下场景,也多想一下孙悟空。

本人技术有限,如果有说不对的地方,欢迎随时指正。
非常欢迎大家留言交流,让我们共同进步~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值