面试官:写一个单例模式

这篇具有很好参考价值的文章主要介绍了面试官:写一个单例模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

面试官:写一个单例模式

1. 什么是单例模式

了解单例模式之前,我们需要先了解什么是设计模式。

设计模式是一种抽象的编程思想,不局限于编程语言,简单来说,就是一些大佬程序猿针对一些典型的场景,给出一些典型的解决方案,只要按照这个方案来,也就是按照给定的设计模式,此时代码写的差,也差不到哪去。

本节讲解的就是设计模式中的其中一种:单例模式

设计模式本质就是一些规章制度,就比如变量命名的风格,如果这个模块要求使用某某设计模式,可以不用吗?当然可以,大不了被队友喷呗,只要内心足够强大。

让我印象特别深的一件事,我还在上学的时候,班里有个人觉得自己写的代码很厉害,往班级群里发,我一看那代码,我靠,shujuyuan,lianbiao,kehuduanqingqiu,这都什么变量名啊,代码能跑吗?能跑,这如果跟我做同事,我指定得喷他。

单例模式很好理解,单例单例,只能有单个实例嘛,就是一个类只能有实例一个对象嘛,不要理解成这个类只能 new 一次哈,而是这个类压根就不让你 new,但是会提供一个给你获取对象的方法,每次获取都是相同的对象!

单例模式的实现常见的有两种实现方式,分别是饿汉模式,懒汉模式,名字奇怪不要紧,后续会讲解。


2. 饿汉模式的实现


public class SingleHungry {
    // 提前创建好对象
    private static final SingleHungry instance = new SingleHungry();

    // 对外隐藏构造方法
    private SingleHungry() {

    }

    // 对外提供获取这个对象的方法
    public static SingleHungry getInstance() {
        return instance;
    }
}

此时饿汉版本的单例模式就实现完成了,阅读上述代码,我们已经提前创建好了 SingleHungry 的实例对象 instance 了,而且使用 private 关键字修饰了构造方法,也就是对外不提供构造方法了,紧接着又写了一个获取 instance 的方法,这样一来,每次获取到的实例都是同一个实例!


public class ThreadDemo {
    public static void main(String[] args) {
        SingleHungry instance1 = SingleHungry.getInstance();
        SingleHungry instance2 = SingleHungry.getInstance();
        System.out.println(instance1.equals(instance2));
    }
}
// 打印结果:true

当然如果你尝试用 SingleHungry instance = new SingleHungry();

这样显然是会报错的。

为什么这种方式叫做饿汉呢?由于 SingleHungry 类里面的 instance 变量是被 static 修饰的,表示是类的属性,所有对象共享的,只存在一份,并且这个 instance 是在类加载的时候被创建的,类加载是比较靠前的阶段,给人的感觉就是很着急,饿得慌,所以就叫做饿汉模式。


3. 懒汉模式的实现

懒汉模式则于饿汉模式相反,饿汉不是很着急吗,老早就把对象创建了,而懒汉模式则是你什么时候需要,我再创建,那么很多小伙伴想到了,这不是很简单吗?直接在 getInstance() 加一个判断嘛,具体代码实现如下:


public class SingleLazy {
    // 不提前创建好对象
    private static SingleLazy instance = null;

    // 对外隐藏构造方法
    private SingleLazy() {

    }

    // 对外提供获取这个对象的方法
    public static SingleLazy getSingleLazy() {
        if (instance == null) {
            instance = new SingleLazy();
        }
        return instance;
    }
}

那么对于懒汉模式来说,什么时候调用 SingleLazy.getSingleLazy() 的时候才会创建 SingleLazy 对象,效率显然是比饿汉模式要高的。


4. 多线程使用单例模式

大家可千万不要忘了,咱们的标题可还是多线程呐,如果单例模式仅仅是在单线程,那执行顺序都是唯一的,显然不会出现线程安全的问题,但是由于多线程环境下的随机调度,抢占式执行,这样一来,可能就会出问题。

此时来判断上述饿汉模式和懒汉模式在多线程环境下是否会出现线程安全的问题呢?

这里我们主要来看这两个版本里最主要的 get 方法的实现:


// 饿汉模式
public static SingleHungry getInstance() {
    return instance;
}

// 懒汉模式
public static SingleLazy getInstance() {
    if (instance == null) {
        instance = new SingleLazy();
    }
    return instance;
}

此时很明显发现,饿汉模式的实现,只要调用 get 方法,就会直接返回,不涉及对 instance 的修改,只涉及读操作,而懒汉模式中涉及到 load,cmp,new,save等,简直就是又有读和写啊。

前面讲线程安全时,涉及到读和写,都有可能出现线程不安全。

就懒汉模式的 getInstance 方法,可以大致分为四个步骤,先读取 instance 的值(load),于 null 做比较(cmp) 条件满足,进行 new 操作(new 也分为好几个步骤),接着在写回 instance(save)。

下面我们就画图模拟两个线程里都调用懒汉模式 getInstance 方法的情况:

面试官:写一个单例模式

上述情况之所以能触发多次 new 操作,本质上是因为比较,读,写这三个操作不是原子的,于是我们就可以通过加锁针对上述 getSingleLazy 方法做优化,保证原子性。


// 懒汉模式
public static SingleLazy getInstance() {
    synchronized (SingleLazy.class) {
        if (instance == null) {
            instance = new SingleLazy();
        }
    }
    return instance;
}

此时我们是解决了原子性的问题,但是这个代码会不会效率很低呢?

每次调用 getSingleLazy 方法的时候,都需要先加锁!加锁操作也是有开销的,那我们有必要每次调用该方法都加锁吗?

其实不用每次都加锁,当 instance 为 null 的时候,需要 new 对象的时候,才需要加锁,只要第一次 new 过对象了,后续都不需要加锁了,因为这个类只能实例一个对象,于是我们就可以在前面再次加上判断语句:


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

写到这里,我们还得思考,这个代码是否有内存可见性的问题,假设同一时间很多线程里面都调用了 getSingleLazy 方法,此时只能有一个线程在进行 new 操作,其他线程都在等待锁释放,那么那么多线程读 instance 发现都是 null。

当锁释放,另一个线程获取到锁,前面发现第一次 if 里面读的 instance 为 null,进入第二个 if 的时候,就没有从内存中再次读 instance,而是直接去 寄存器/cache 里读,所以仍然判断 instance 为 null,也会出现重复 new 对象的操作。

再者,instance = new SingleLazy(); 这个操作可以大致拆分成三个步骤:

1. 申请内存空间
2. 调用对应构造方法,初始化内存空间
3. 把内存空间的地址赋值给 instance 引用

如果编译器优化,指令重排序了,本来是按照 1 2 3 的顺序,如果优化成了 1 3 2 的顺序,在多线程的情况,1 2 3 顺序和 1 3 2 是一样的结果,但是多线程环境可不一定了!

假设 t1 线程按照 1 3 2 的顺序去执行,执行完 3 这个操作后,此时 instance 里只是存了一个地址,但地址对应的内存空间并没有初始化,此时如果 t1 被 CPU 切走了(CPU调用其他线程了),CPU 开始调用 t2 线程执行 1 2 3 顺序,此时 t2 就发现 instance 里不为 null,直接 return instance; 但是这个 instance 对应的内存空间并未初始化,所以 t2 调用 getInstance() 得到的就是一个非法的对象(未初始化的对象)。

面试官:写一个单例模式

如上可知,我们要避免出现内存可见性和指令重排序的问题,这时就可以用我们前面学到过的 volatile 关键字,来保证内存可见性,禁止指令重排序。

最终完整版的懒汉模式代码如下:


public class SingleLazy {
    // 不提前创建好对象
    private volatile static SingleLazy instance = null;

    // 对外隐藏构造方法
    private SingleLazy() {

    }

    // 对外提供获取这个对象的方法
    public static SingleLazy getSingleLazy() {
        if (instance == null) {
            synchronized (SingleLazy.class) {
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }
}

当然这个代码还不完整!虽然我们构造方法是私有的,但是利用反射仍然可以构造多个对象,这里可以采用枚举来防止被反射,还有防止序列化的情况,但是反射是非常规编程手段,如果被面试官要求写个懒汉模式,其实写到这,已经够了!


下期预告:【多线程】生产者消费者模型文章来源地址https://www.toymoban.com/news/detail-462817.html

到了这里,关于面试官:写一个单例模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JavaSE面试题02:单例设计模式

    来源: https://www.runwsh.com/archives/biitngg1f7s0000 1.什么事Singleton? Singleton:在Java中 即指单例设置模式,探视软件开发最常用的设置模式之一 通俗解释:单例模式 单:唯一 例:实例 单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式 例如:代表

    2024年02月05日
    浏览(41)
  • 一天一个设计模式---单例模式

    单例模式是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这意味着在应用程序中的任何地方,只能有一个实例存在,而不会创建多个相同类型的实例。 单例模式通常包括以下几个要素: 私有构造函数(Private Constructor): 单例类的构

    2024年02月02日
    浏览(41)
  • 单例模式:保证一个类只有一个实例

    在软件开发中,有些类 只需要一个实例 ,比如数据库连接池、线程池等。单例模式就是一种设计模式,用于 确保一个类只有一个实例,并提供一个全局访问点 。 1. 饿汉式 饿汉式是最简单的实现方式,它在类加载时就创建了实例,并且提供一个静态方法返回该实例。 jdk源码

    2024年02月14日
    浏览(41)
  • 【设计模式学习1】什么是单例模式?单例模式的几种实现。

    单例模式是在内存中只创建一个对象的模式,它保证一个类只有一个实例。 如上图所示,多线程情况下,在时刻T,线程A和线程B都判断single为null,从而进入if代码块中都执行了new Single()的操作创建了两个对象,就和我们当初的单例初衷相悖而行。 1、第一次判空目的:为了缩

    2024年02月15日
    浏览(56)
  • 什么是单例模式

    所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。 单例模式有两种方式:1) 饿汉式 2) 懒汉式 演示 饿汉式 和 懒汉式 单例模式的实现。 步骤如下: 构造器私有化 =》 防止其

    2024年02月12日
    浏览(47)
  • 什么是单例模式,如何在Java中实现单例?

    单例模式是一种创建对象的设计模式,它确保只有一个实例,并提供对该实例的全局访问点。在Java中实现单例有懒汉式和饿汉式两种方式。 懒汉式单例模式: 在懒汉式单例模式中,对象创建在第一次使用时才会被创建。例如,当某个类第一次被实例化时,JVM会在堆内存中分

    2024年02月15日
    浏览(56)
  • 01、创建型-单例模式--只有一个实例

      单例模式是设计模式中最简单但又最常用的的设计模式之一,是很多人学的第一个设计模式。引用百度百科的定义:单例模式创建的类在当前进程中,保证一个类只会被实例化一次,并提供了全局访问点,使用的时候通过单例提供的方法来获取实例。在确保线程安全的前

    2024年04月27日
    浏览(37)
  • Java 设计模式 - 单例模式 - 保证类只有一个实例

    单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。在某些情况下,我们需要确保一个类的实例在整个应用程序中是唯一的,这时候单例模式就非常有用。在本篇博客中,我们将详细探讨单例模式的概念、实现方式以及如何在

    2024年02月15日
    浏览(45)
  • Java面试之单例模式的六种实现方式

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 由于设计模式在面向对象中起着举足轻重的作用,在面试中很多公司都喜欢问一下有关设计模式的问题。在常用的设计模式中,Singleton单例模式是唯一一个能用短短几十行代码完整实现的模式,因此,写

    2024年02月10日
    浏览(44)
  • 深入理解单例模式:如何确保一个类只有一个实例?

    欢迎来到英杰社区 https://bbs.csdn.net/topics/617804998 欢迎来到阿Q社区 https://bbs.csdn.net/topics/617897397 单例模式(Singleton Pattern)是一种常用的设计模式,用于确保一个类只有一个实例,并提供全局访问点。虽然在表面上看起来很简单,但深入理解单例模式可以帮助我们更好地应用它

    2024年01月17日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包