草庐IT

面试必问设计模式之单例模式(超详细)

janyxe 2023-12-30 原文

设计模式系列文章目录

如果本文对你们的开发之路有所帮助,请帮忙点个赞,您的支持是我坚持写博客的动力

【设计模式相关书籍】wx关注【Java从零学架构】,后 台回复【设计模式】自取

前言

上一篇文章带着大家输入学习了设计模式的工厂模式,这篇文章带着大家深入单例模式

项目代码见 https://gitee.com/janyxe/design_patterns

什么是单例模式

  • 保证一个类只有一个实例,并且提供一个全局访问点

  • 当前JVM中只会有一个实例对象

能画出单例模式类图吗?并简单说明下

单例模式保证一个类只有一个实例

说说单例模式的应用场景

  • Servlet对象 默认为单例
  • 多线程的线程池的设计采用单例模式
  • Spring中Bean对象默认为单例模式
  • 定义枚举常量信息
  • 项目配置文件为单例模式

单例模式有哪些创建方式?

恶汉式懒汉式饿汉式(线程安全)双重检验锁枚举内部类静态类

懒汉模式

概念

延迟加载,只有真正使用的时候,才开始实例化

懒汉模式实现

线程不安全版本

代码见 cn.jany.singleton.slacker.SlackerSingleton

/**
 * 懒汉模式线程不安全实现
 */
public class SlackerSingleton {

    private static SlackerSingleton slackerSingleton = null;

    private SlackerSingleton(){

    }

    /**
     * 获取实例方法
     * @return
     */
    public static SlackerSingleton getInstance(){
        if (slackerSingleton == null){
            // 当获取实例为null时创建实例
            slackerSingleton = new SlackerSingleton();
        }
        return slackerSingleton;
    }

    public static void main(String[] args) throws InterruptedException {
        SlackerSingleton slackerSingleton = SlackerSingleton.getInstance();
        SlackerSingleton slackerSingleton2 = SlackerSingleton.getInstance();
        // 控制台输出true,表示获取的实例为同一个
        System.out.println(slackerSingleton == slackerSingleton2);
    }
}

控制台输出true,代表两个对象一致

在多线程环境中存在多个对象 ,线程不安全

代码见 cn.jany.singleton.slacker.SlackerSingleton1

public class SlackerSingleton1 {


    public static void main(String[] args){

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                SlackerSingleton slackerSingleton = SlackerSingleton.getInstance();
                System.out.println(slackerSingleton);
            }
        });


        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                SlackerSingleton slackerSingleton = SlackerSingleton.getInstance();
                System.out.println(slackerSingleton);
            }
        });

        // 创建2个线程
        thread.start();
        thread2.start();
    }
}

输出对象不同

cn.jany.singleton.slacker.SlackerSingleton@4909e27e
cn.jany.singleton.slacker.SlackerSingleton@593fdda8
线程安全版本

通过synchronized关键字可以保证线程安全,不过效率相对较低

代码见 cn.jany.singleton.slacker.SlackerSingleton2

public class SlackerSingleton2 {

    private static SlackerSingleton2 slackerSingleton2 = null;

    public static synchronized  SlackerSingleton2 getInstance(){
        if (slackerSingleton2 == null){
            slackerSingleton2 = new SlackerSingleton2();
        }
        return slackerSingleton2;
    }

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

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SlackerSingleton2.getInstance());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SlackerSingleton2.getInstance());
            }
        }).start();
    }

}
懒汉式双重检验锁
  • 通过双重检验锁加锁优化

  • 编译器(JIT),CPU 有可能对指令进行重排序
    类加载过程:分配空间、初始化、引用赋值
    重排序之后可能的结果:分配空间、引用赋值(多线程其他线程获取实例可能为未初始化的实例)、初始化
    通过volatile关键字进行修饰可以防止重排序

代码见 cn.jany.singleton.slacker.SlackerSingleton3

public class SlackerSingleton3 {

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

}
class Singleton{
    
    /**
     * volatile 防止重排序
     */
    private volatile static Singleton singleton = null;

    /**
     * 构造函数
     */
    private Singleton(){

    }

    /**
     * 获取实例
     * @return
     */
    public static Singleton getInstance(){
       // 第一层校验
       if (singleton == null){
           synchronized (Singleton.class){
               // 第二层校验
               if (singleton == null){
                   singleton = new Singleton();
               }
           }
       }
       return singleton;
    }


}

恶汉模式

概念

  • 类加载的 初始化阶段就完成了实例的初始化。

  • 通过jvm加载机制保证实例唯一性(初始化只会执行一次)和线程安全(JVM 以同步方式完成类加载过程)

类加载过程会经过:

加载验证准备解析初始化使用卸载

类加载过程各阶段解析:

  • 加载:在硬盘查找并通过IO读取字节码文件,在加载节点生成这个类的java.class.Class对象
  • 验证:校验字节码文件的准确性
  • 解析:讲符号引用替换为直接引用
  • 初始化:对类的静态变量初始化为指定的值,执行静态代码块

只有在真正使用对应的类时,才会触发初始化

恶汉模式实现

恶汉模式实现方式一

代码见 cn.jany.singleton.villain.VillainSingleton

/**
 * 恶汉模式
 */
public class VillainSingleton {

    public static final  VillainSingleton villainSingleton = new VillainSingleton();

    /**
     * 构造方法
     */
    private VillainSingleton(){

    }

    /**
     * 获取实例
     * @return
     */
    private static VillainSingleton getInstance(){
        return villainSingleton;
    }

    public static void main(String[] args) {
        VillainSingleton instance = VillainSingleton.getInstance();
        VillainSingleton instance2 = VillainSingleton.getInstance();
        System.out.println(instance == instance2);
    }
}

控制台输出

true
恶汉模式实现方式二

代码见 cn.jany.singleton.villain.VillainSingleton2

/**
 * 恶汉模式
 */
public class VillainSingleton2 {

    public static final VillainSingleton2 singleton = new VillainSingleton2();

    /**
     * 构造函数
     */
    private VillainSingleton2(){

    }

    public static void main(String[] args) {
        VillainSingleton2 singleton = VillainSingleton2.singleton;
        VillainSingleton2 singleton1 = VillainSingleton2.singleton;
        System.out.println(singleton == singleton1);
    }
}

控制台输出

true

静态内部类

概念

  • 利用类的加载机制保证线程安全
  • 只有在实际使用的时候,才会触发类的初始化

静态内部类实现

代码见 cn.jany.singleton.inner.InnerClassSingletonTest

public class InnerClassSingletonTest {

    public static void main(String[] args) {
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        InnerClassSingleton instance2 = InnerClassSingleton.getInstance();
        System.out.println(instance == instance2);
    }

}

class InnerClassSingleton{

    static {
        System.out.println("InnerClassSingleton static ");
    }

    private InnerClassSingleton(){
    }

    public static InnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static InnerClassSingleton instance= new InnerClassSingleton();

        static {
            System.out.println( "SingletonHolder static" );
        }
    }
}

控制台输出

InnerClassSingleton static 
SingletonHolder static
true

静态代码块

静态代码块实现

代码见 cn.jany.singleton.inner.StaticSingleton

/**
 * 静态代码块
 */
public class StaticSingleton {

    private static StaticSingleton staticSingleton;

    static {
        staticSingleton = new StaticSingleton();
    }

    public static StaticSingleton getInstance(){
        return staticSingleton;
    }

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

控制台输出

true

枚举实现单例

代码见 cn.jany.singleton.enums.EnumSingletonTest

枚举实现单例实现

/**
 * 枚举实现单例模式
 */
public enum  EnumSingleton {

    INSTANCE;

}
public class EnumSingletonTest {

    public static void main(String[] args) {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        System.out.println(instance == instance1);
    }
}

控制台输出

true

有关面试必问设计模式之单例模式(超详细)的更多相关文章

  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. 在VMware16虚拟机安装Ubuntu详细教程 - 2

    在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主

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

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

  10. 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

随机推荐