草庐IT

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

左耳朵梵高 2023-03-28 原文

设计模式-单例模式

《巫师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才被加载,这个时候才会创建instance。instance的唯一性、创建过程的线程安全有JVM虚拟机来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

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

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

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

public enum Singleton{
    INSTANCE;
}

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

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

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

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

重点回顾

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

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



有关《我是面试官》设计模式-单例模式的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  7. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  8. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  9. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  10. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

随机推荐