首页 > 技术文章 > 设计模式-单例(Singleton)

Ishton 2021-03-27 13:56 原文

本文总结参考[https://mp.weixin.qq.com/s/dDAAH-Vkxi8xoj6uT0XRtg],如有问题,请联系删除!
单例,顾名思义,整个系统其实就只有一个实例存在,不能再多,否则就不叫单例。
一个God类

public class God {

}

得保证任何人都不能去创建神的实例,把构造方法改成private,也就是神可以自己创造自己,但别人不能。

public class God {
    private God(){}//构造方法私有化
}

God类里面封装一个God自己,神自己造自己。

public class God {
    private static final God god = new God();//自有永有的神单例
    private God(){}//构造方法私有化
}

以上private关键字保证了上帝的私有性,不可见性,不可访问性。static关键字保证上帝的静态性,他与类同在,不依赖于类的实例化就自有永有,他将在内存中永生,GC垃圾回收器也回收不了他。final关键字则保证这位神是和常量,衡量,他是终极上帝,不能再改。

需要一个静态方法getInstance()来请神

public class God {
    private static final God god = new God();//自有永有的神单例
    private God(){}//构造方法私有化
    public static God getInstance(){//请神方法公开化
        return god;
    }
}

方法体内我们就返回这个在唯一的真神,当然方法它必须是public公开的。

懒汉模式(Lazy load)

public class God {
    private static God god;//这里不进行实例化
    private God(){}
    public static God getInstance() {
        if (god == null) {//如果无神才造神
            god = new God();
        }
        return god;
    }
}

一开始就没有造神,只有某人第一次求神时才实例化,之后再求的就直接返回了。这样的好处是省了一段时间的内存(无求神期间),坏处是第一次请神的时候速度相较之前的痴汉模式会慢,因为要消耗CPU去造神。

其实这么写是在多线程模式下是有陷阱的,试想多人同时并发请神的话,依然会造成多神,好吧我们再来改良一下,把请神方法加上synchronized,声明为同步方法,某线程调用前必须获取同步锁,调用完后会释放锁给其他线程用,也就是请神的必须排队,大家一个一个按顺序来。

public class God {
    private static God god;//这里不进行实例化
    private God(){}
    public static synchronized God getInstance() {//此处加入同步
        if (god == null) {//如果无神才造神
            god = new God();
        }
        return god;
    }

然而,这样做是要付出代价的,还没进庙呢不管三七二十一请神的直接给加锁排队,结果队伍从北边的庙排到了南天门,人们都要来一个一个拜佛求神,这造成了巨大时间浪费,没有充分利用CPU资源并发优势(特别是多核情况)。好吧,那还是让人们抢好了,但依然得保证单例神的情况下。

去掉方法上的同步关键字,换到方法体内部做同步,整个方法开放并发大家都可以同时入庙,当然起早贪黑的虔诚信徒们要抢头香是必须要入堂排队的。一旦头香诞生,那其他抢香的都白早起,白排队了。再之后的事情我们都可以预见了,头注香被抢后堂内排队再无必要来了,大家可以在堂外同时并发拜佛求神,这就极大的利用了CPU资源。简而言之:只有第一批抢头香的在排队,之后大家都不必排队了,代码如下。

public class God {
    private volatile static God god;
    private God(){} 
    public static God getInstance() {//庙是开放的不用排队进入
        if (god == null) {//如果头柱香未产生,这批抢香人进入堂内排队。
            synchronized(God.class){
                if (god == null) {//只有头香造了神,其他抢香的白排队了
                    god = new God();
                }
            }
        }
        //此处头柱香产生后不必再排队
        return god;
    }
}

倾向于痴汉模式,现在内存成本根本不算问题,况且迟早要被实例化占用内存,加锁解锁更是一种浪费,还有同步效率低等问题,如果上帝不是很占空间那就没必要去懒汉延迟加载,越复杂问题越多,风险越大。

单例模式优点:
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。

缺点:
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

推荐阅读