《我是面试官》设计模式-单例模式

设计模式-单例模式

image

《巫师3》中,陪着主人公南征北战的坐骑,不管你何时何地召唤它,它永远只有一个名字——萝卜。

面试开始

HR :来了一个面试Java的,我让他在小会议室等着了。

面试官 :好的,我就来。

面试官用一次性纸杯倒了杯水,夹着Mac,进了小会议室。看见一个20出头的精神小伙,带着黑框眼镜,发量诱人,像极了N年前的自己,风华正茂,书生意气。

面试官 :你好,先喝杯水吧。(不给应聘者倒水的公司都是不靠谱的)我看你简历上写着精通设计模式,要不我们就聊聊设计模式吧。

应聘者 :可以呀。

一句轻描淡写的“可以呀”,但经验丰富的面试官还是发现了平静面容下,应聘者的一丝丝窃喜,好像很胸有成竹的样子。

面试官 :那就说说,你平时都用了哪些设计模式吧?

应聘者 :(内心狂喜ing)我平时使用最多的设计模式有单例模式。单例模式属于23种设计模式中的创建型的设计模式。23种设计模式可以分为3种:创建型、结构型和行为型。单例模式确保了一个类只有一个实例。单例模式有5种实现方式:懒汉式、饿汉式、Double-Check方式、静态内部类方式、枚举方式。

面试官 :嗯,你对单例模式了解的不错嘛。你先说下为什么要使用单例模式吧。

应聘者 :单例模式其实很简单,就是一个类只能创建一个实例。在程序中,有一些对象只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。还有些业务上就只会有一个,比如公司主体等。

面试官 :那如何实现一个单例呢?

应聘者 :实现单例有好几种方式,有饿汉式、懒汉式、静态内部类,或者使用枚举来实现。使用单例模式,一般把类的构造函数设置为private,避免通过new创建多个示例。我先来说下饿汉方式吧。

应聘者喝了口水,似乎准备开始表演了。

应聘者 :饿汉式实现比较简单。类有一个静态的实例,一般取名为instance。在类加载的时候,就会创建并初始化好instance实例。所以,饿汉式是线程安全的。

面试官 :你能写一下具体的实现代码吗?

应聘者很快就在纸上写出了饿汉式的代码实现:

public static Singleton{
    private static final Singleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

看的出来,应聘者对饿汉式的代码实现很熟悉,编码风格和命名也很不错。我在面试的时候,就有几位应聘者不知道如何给类命名,有的使用Danli,有的使用Single或One。

面试官 :嗯,很不错。你平时都使用这种方式吗?

应聘者 :哦,不是的。饿汉式虽然简单,但是有个问题是,它不支持延迟加载,或者叫按需加载。在系统启动时,就必须要创建实例。

面试官 :这样会有什么问题吗?

应聘者 :如果实例占用资源多,比如内存占用高,或者初始化耗时长(比如需要加载各种配置文件),提前初始化就会造成浪费。应该在用到的时候再去初始化。

面试官 :如果初始化耗时长,等用到的时候再初始化。就可能在用户请求接口的时候,触发了这个初始化过程,会导致请求的响应时间很长,甚至超时。对用户造成影响。所以,究竟是启动时初始化好,还是延迟初始化好呢?

应聘者 :啊,这个。。。(这个面试官不按套路出牌呀)网上说的都是要延迟加载。

面试官 :还有,如果实例占用资源多,比如内存使用高。如果延迟加载,可能会出现在程序运行一段时间后,因为初始化实例,占用资源多,出现了OOM,程序崩溃。根据Fast Fail原则,是不是就应该在启动时初始化实例,如果资源不够,我们就能快速发现问题,尽快进行修复,而不会让问题在生产环境中才暴露。

应聘者 :嗯,好像有道理。但我看网上的文章都说这种方式不好。

面试官 :那你觉得哪种方式好呢?

应聘者 :(内心有些摇摆,有些凌乱)

面试官 :那我们再聊聊延迟加载的单例?

应聘者 :嗯嗯,好呀。延迟加载就是在使用的时候才进行初始化,它的代码实现是这样的:

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

面试官 :嗯,不错嘛。我看getInstance方法中,有多个null判断,还有个synchronized锁,能不能解释一下。

应聘者 :这个叫双重检查(Double Check)。加synchronized是为了保证线程安全。null判断是为了提升性能。如果不在前面先判断instance是否为null,就需要在每次使用时,先获取锁,然后释放锁,会导致性能瓶颈。

应聘者 :所以使用了双重检查,只要instance被创建后,即使再调用getInstance,也不会再加锁了。解决了性能问题。

应聘者 :网上有人说,这种实现方式也有问题。因为指令重排,可能会导致Singleton被new出来后,被赋值给了instance,还没来得及初始化,就被另一个线程使用了,可能会出现NPE错误。要解决这个问题,我们需要给instance成员变量添加volatile关键字,禁止指令重排。

面试官 :嗯,你对Java指令重排也有了解呀,不错。关于线程安全,我们稍后再仔细聊聊吧。

应聘者 :(不要啊,我就只记住了这一段。待会儿一聊就露馅了啊。。。)

面试官:你知道还有其它实现单例的方式吗?

应聘者 :还有个静态内部类方式。它比双重检查更加简单。就是利用Java的静态内部类。代码实现是这样的:

public class Singleton{
    private Singleton(){}
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

应聘者SingletonHolder是一个内部静态类,当外部Singleton被加载时,并不会创建SingletonHolder实例对象。只有当调用getInstance方法时,SingletonHolder才被加载,这个时候才会创建instanceinstance的唯一性、创建过程的线程安全有JVM虚拟机来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

应聘者 :还有一种使用枚举创建单例的方式。

面试官 :哇,还有嘛。那你再说说吧。

应聘者 :使用枚举应该是最简单的。它利用了Java枚举类型本身的特点,保证了实例创建的线程安全和实例唯一性。代码如下:

public enum Singleton{
    INSTANCE;
}

面试官 :你平时都是使用这个方式吗?

应聘者 :没有呢。这种方式的确简单,而且也是《Effective Java》作者推荐的。但是我觉得用枚举来表达一个单例,这种方式比较奇怪。总觉得是一样投机取巧的方式。

面试官 :哈哈哈。。的确是这样,开源项目中也很少会使用这种方式,是比较怪。你对单例模式的理解很深入呀,说出了这么多种实现,不错不错。刚才看你对线程安全也挺了解的,那我们接下来再聊聊Java多线程吧。

应聘者 :(狠狠抽了几下耳巴子。。。叫你多嘴。。。)

重点回顾

单例模式是面试中经常出现的话题。单例模式本身比较简单,就是一个类只有一个实例。大部分面试者在面试准备时,都会阅读单例的相关知识点,比如单例模式的多种实现。

但是,希望大家不要仅仅是背诵,还应该多去理解。本文的面试中,面试官问了一个问题,到底是启动时初始化好,还是延迟加载好呢?这个问题,大家可以自己思考一下。


每日一画:在麦当劳送外卖的梵高

大家好,我是左耳朵梵高,北理工毕业,本是导弹专业,意外进入了IT行业。现任某金融咨询公司首席架构师,曾在阿里巴巴中间件团队任职。沉浸软件行业十余年,热衷于软件架构、研发效能、分布式、云原生等领域,相信技术能改变世界。译有《你真的会写代码吗?》

坚持输出技术干货,职场心得和读书感悟。欢迎关注「左耳朵梵高」,和我一起持续学习,终生成长。

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

推荐阅读更多精彩内容