寂然解读设计模式 - 单例模式(上)

I walk very slowly, but I never walk backwards 

设计模式 - 单例模式(上)


寂然

大家好~,我是寂然,本节课呢,我们正式进入设计模式的学习,第一个要聊的就是我们最常见的,同时也是面试高频的单例模式,那老规矩,我们直接启程吧

五重境界

在此之前,我们闲聊两句,因为有的小伙伴问到一个问题,怎么才算是学会或者说掌握设计模式了呢?那我们一起来聊聊这个话题,会有很多人说其实这个不好去定义,但是如果自己平时写代码,无意识,习惯成自然的用到了设计模式,遇到实际问题,很快就能对比选择,使用合适的设计模式,低耦合,合理的解决掉实际问题,我觉得就是很好的掌握了,网上有人分享了设计模式的五重境界,感觉很贴切,这里也拿来和大家分享下


第 1 层:刚开始学编程不久,听说过什么是设计模式 ,但不了解

第 2 层:有很长时间的编程经验了,自己写了很多代码,其中用到了设计模式,但是自己却不知道

第 3 层:学习过了设计模式,发现自己已经在使用了,并且发现了一些新的模式挺好用的

第 4 层:阅读了很多别人写的源码和框架,其中看到了设计模式,能够领会设计模式的精妙和带来的好处

第 5 层:代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来

问题答疑

同时,在开始之间还要和大家强调一句话,因为很多小伙伴都有说或者脑海中有这样一个疑问:这样就可以啊,为什么非要这么复杂呢,大家要注意,设计模式不仅仅是站在实现功能的角度上,而是立足于整个软件系统的设计,所以大家要学会在学习的过程中,总结和对比思考,可以达到事半功倍的效果

官方定义

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例并且该类只提供一个取得其对象实例的方法(静态方法)


举个最常见的例子,Spring 中的 bean 默认都是单例模式,每个bean定义只生成一个对象实例,每次getBean请求获得的都是此实例

单例模式八种方式

那接下来我们来聊聊单例模式的八种实现方式,如下所示

饿汉式(静态常量)

饿汉式(静态代码块)

懒汉式(线程不安全)

懒汉式(线程安全,同步方法)

懒汉式(线程安全,同步代码块)

双重检查

静态内部类

枚举方式

饿汉式(静态常量)

饿汉式之静态常量的写法如下:

一,构造器私有化(防止外部通过new + 构造器的方式创建对象实例)

二,类的内部创建对象(创建final,static的实例对象)

三,对外暴露一个公共的静态方法(通过该方法,返回该类唯一的对象实例)

// 饿汉式(静态变量)
class Singleton{
    //一 构造器私有化
    private Singleton(){

    }
    //类的内部创建对象
    private final static Singleton singleton = new Singleton();

    //对外提供公共的,静态的方法
    public static Singleton getInstance(){

            return singleton;
    }
}

public class SingletonDemo {
    public static void main(String[] args) {

        //测试
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
        //结果为 true,证明是同一个实例对象
    }
}
写法分析

优势

这种写法比较简单,就是在类装载的时候就完成了实例化,避免了线程同步问题

这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大 多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果

劣势

在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果,如果从始至终从未使用过这个实例,则会造成内存的浪费

饿汉式(静态代码块)

饿汉式(静态代码块)的方式,是在静态代码块中创建单例对象,示例代码如下图所示

//饿汉式 静态代码块
class Singleton{

    private Singleton(){

    }

    private final static Singleton singleton;

    static {
        singleton = new Singleton();
    }

    public static Singleton getInstance(){

        return singleton;
    }
}

public class Singletondemo02 {

    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);

    }
}
写法分析

这种方式和上面的其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例,优劣势和第一种写法一致,写法可用,但可能会造成内存的浪费

饿汉式是不存在线程安全问题的

懒汉式(线程不安全)

懒汉式写法与饿汉式写法的不同在于,并不是一上来就要创建对象,而是当调用对应的getInstance()方法时,才会进行创建,示例代码如下图所示

// 懒汉式(线程不安全)
class Singleton{
    
    //构造器私有化
    private Singleton(){ 
    
    }

    //类的内部创建对象
    private static Singleton singleton;

    //提供一个静态的公有方法,当使用到该方法时,才会去创建 instance
    public static Singleton getInstance(){

        fiif (singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

public class SingletonDemo {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);

    }
}
写法分析

优势

起到了 lady loading 的效果,不会造成内存浪费,实例使用到的时候,调用getInstance()方法才会创建

劣势

只能在单线程下使用,多线程会出现线程不安全的问题

试想,如果在多线程下,一个线程进入了 if (singleton == null){ ...} 判断语句块,还没来得及往下执行,另一个线程进行判断,此时实例还未来得及创建,也通过了这个判断语句,这时便会产生多个实例,出现了线程不安全问题

所以,在实际开发中,不建议使用这种方式

懒汉式(同步方法)

针对上面线程不安全的问题,我们在懒汉式的代码中,加入同步处理,第一种就是同步方法,相关代码如下

//懒汉式 同步方法
class Singleton{

    //构造器私有化
    private Singleton(){

    }

    //类的内部创建对象
    public static Singleton singleton;

    //对外提供公共的静态方法,加入同步处理
    public static synchronized Singleton getInstance(){

        if (singleton == null){
            singleton = new Singleton();  
        }
        return singleton;
    }
}

public class SingletonDemo02 {
    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
    }
}
写法分析

首先,这种写法,由于加入了同步处理,解决了线程不安全的问题

但是我们说,这样效率很低,每个线程在想获得类的实例的时候,执行 getInstance() 方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类的实例,直接return就好了,方法进行同步,效率很低

所以,在实际开发中,也不推荐使用这种方式

懒汉式(同步代码块)

那既然方法进行同步,效率很低,我们可不可以使用同步代码块的方式呢?大家看如下的示例代码

class Singleton{

    //构造器私有化
    private Singleton(){ }

    //类的内部创建对象
    public static Singleton singleton;

    //对外提供公共的静态方法
    public static Singleton getInstance(){

        if (singleton == null){

            synchronized (Singleton.class){
                singleton = new Singleton();  
            }

        }
        return singleton;
    }
}
写法分析

这种写法就是,本意是想对第四种实现方式的改进,在创建实例对象的时候,进行同步,那大家思考一下,这样可行嘛?可以保证线程安全嘛


但其实,如果在多线程下,一个线程进入了 if (singleton == null){ ...} 判断语句块,还没来得及往下执行,另一个线程进行判断,此时实例还未来得及创建,也通过了这个判断语句,便会产生多个实例,所以在里面创建实例对象的时候,进行同步没有实际意义,在实际开发中,不能使用这种方式

下节预告

OK,由于篇幅的限制,本节内容就先到这里,下一节,我们接着来聊单例模式的后三种写法,包括双重检查机制,静态内部类,枚举,同时会带大家看下JDK源码中单例模式的应用,以及对单例模式的注意事项进行总结,最后,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335