java面试常见模式问题---单例模式

1、简介

单例模式使⽤场景

  • 业务系统全局只需要⼀个对象实例,⽐如发号器、 redis 连接对象等。
  • Spring IOC容器中的 Bean 默认就是单例。
  • Spring Boot 中的 Controller、Service、Dao 层中通过 @Autowire的依赖注⼊对象默认都是单例的。

单例模式分类

  • 懒汉:就是所谓的懒加载,延迟创建对象,需要用的时候再创建对象。
  • 饿汉:与懒汉相反,提前创建对象。
  • 单例模式实现步骤:
  • 私有化构造函数
  • 提供获取单例的方法。

2、单例模式——懒汉式

单例模式——懒汉式有以下⼏种实现⽅式:

/**
 * @Auther: csp1999
 * @Date: 2020/11/06/20:36
 * @Description: 单例设计模式-懒汉式
 */
public class SingletonLazy {
    // 当需要用到该实例的时候再创建实例对象
    private static SingletonLazy instance;
    /**
     * 构造函数私有化
     * 不能通过 new SingletonLazy() 的方式创建实例
     *
     * 当需要用到该实例的时候在加载
     * 只能通过 SingletonLazy.getInstance() 这种方式获取实例
     */
    private SingletonLazy() {
    }
    /**
     * 单例对象的方法
     */
    public void process() {
        System.out.println("方法实例化成功!");
    }
    /**
     * 方式一:
     * <p>
     * 对外暴露一个方法获取该类的对象
     * <p>
     * 缺点:线程不安全,多线程下存在安全问题
     *
     * @return
     */
    public static SingletonLazy getInstance() {
        if (instance == null) {// 实例为null时候才创建
            /**
             * 线程安全问题:
             * 当某一时刻,两个或多个线程同时判断到instance == null成立的时候
             * 这些线程同时进入该if判断内部执行实例化
             * 则会新建出不止一个SingletonLazy实例
             */
            instance = new SingletonLazy();// 当需要的时候再进行实例化对象
        }
        return instance;
    }
    /**
     * 方式二:
     * 通过加synchronized锁 保证线程安全
     *
     * 采用synchronized 对方法加锁有很大的性能开销
     * 因为当getInstance2()内部逻辑比较复杂的时候,在高并发条件下
     * 没获取到加锁方法执行权的线程,都得等到这个方法内的复杂逻辑执行完后才能执行,等待浪费时间,效率比较低
     *
     * @return
     */
    public static synchronized SingletonLazy getInstance2() {
        if (instance == null) {// 实例为null时候才创建
            // 方法上加synchronized锁后可以保证线程安全
            instance = new SingletonLazy();// 当需要的时候再进行实例化对象
        }
        return instance;
    }
    /**
     * 方式三:
     * 在getInstance3()方法内,针对局部需要加锁的代码块加锁,而不是给整个方法加锁
     *
     * 也存在缺陷:
     * @return
     */
    public static SingletonLazy getInstance3() {
        if (instance == null) {// 实例为null时候才创建
            // 局部加锁后可以保证线程安全,效率较高
            // 缺陷:假设线程A和线程B
            synchronized (SingletonLazy.class){
                // 当线程A获得锁的执行权的时候B等待 A执行new SingletonLazy();实例化
                // 当A线程执行完毕后,B再获得执行权,这时候还是可以实例化该对象
                instance = new SingletonLazy();// 当需要的时候再进行实例化对象
            }
        }
        return instance;
    }
}

单例模式:懒汉实现 + 双重检查锁定 + 内存模型

对于上面方式三存在的缺陷,我们可以使用双重检查锁定的方式对其进行改进

/**
 * 方式三改进版本:
 * 在getInstance3()方法内,针对局部需要加锁的代码块加锁,而不是给整个方法加锁
 *
 * DCL 双重检查锁定 (Double-Checked-Locking) 在多线程情况下保持高性能
 *
 * 这是否安全? instance = new SingletonLazy(); 并不是原子性操作
 * jvm中 instance实例化内存模型流程如下:
 * 1.分配空间给对象
 * 2.在空间内创建对象
 * 3.将对象赋值给instance引用
 *
 * 假如出现如下顺序错乱的情况:
 * 线程的执行顺序为:1 -> 3 -> 2, 那么这时候会把值写回主内存
 * 则,其他线程就会读取到instance的最新值,但是这个是不完全的对象
 * (指令重排现象)
 *
 * @return
 */
public static SingletonLazy getInstance3plus() {
    if (instance == null) {// 实例为null时候才创建
        // 局部加锁后可以保证线程安全,效率较高
        // 假设线程A和线程B
        synchronized (SingletonLazy.class){// 第一重检查
            // 当线程A获得锁的执行权的时候B等待 A执行new SingletonLazy();实例化
            // 当A线程执行完毕后,B再获得执行权,这时候再判断instance == null是否成立
            // 如果不成立,B线程无法 实例化SingletonLazy
            if (instance == null){// 第二重检查
                instance = new SingletonLazy();// 当需要的时候再进行实例化对象
            }
        }
    }
    return instance;
}

再次升级方式三,来解决内存模型中的指令重排问题

// 添加volatile 关键字,禁止实例化对象时,内存模型中出现指令重排现象
private static volatile SingletonLazy instance;
/**
 * 方式三再次升级版本:
 * 在getInstance3()方法内,针对局部需要加锁的代码块加锁,而不是给整个方法加锁
 *
 * DCL 双重检查锁定 (Double-Checked-Locking) 在多线程情况下保持高性能
 *
 * 解决指令重排问题——禁止指令重排
 * @return
 */
public static SingletonLazy getInstance3plusplus() {
    if (instance == null) {// 实例为null时候才创建
        // 局部加锁后可以保证线程安全,效率较高
        // 假设线程A和线程B
        synchronized (SingletonLazy.class){// 第一重检查
            // 当线程A获得锁的执行权的时候B等待 A执行new SingletonLazy();实例化
            // 当A线程执行完毕后,B再获得执行权,这时候再判断instance == null是否成立
            // 如果不成立,B线程无法 实例化SingletonLazy
            if (instance == null){// 第二重检查
                instance = new SingletonLazy();// 当需要的时候再进行实例化对象
            }
        }
    }
    return instance;
}

单例模式——懒汉式调用:

@Test
public void testSingletonLazy(){
    SingletonLazy.getInstance().process();
}

3、单例模式——饿汉式

/**
 * @Auther: csp1999
 * @Date: 2020/11/06/21:39
 * @Description: 单例设计模式-饿汉式
 */
public class SingletonHungry {
    // 当类加载的时候就直接实例化对象
    private static SingletonHungry instance = new SingletonHungry();
    private SingletonHungry(){}
    /**
     * 单例对象的方法
     */
    public void process() {
        System.out.println("方法实例化成功!");
    }
    public static SingletonHungry getInstance(){
        return instance;// 当类加载的时候就直接实例化对象
    }
}

单例模式——饿汉式调用:

@Test
public void testSingletonHungry(){
    SingletonHungry.getInstance().process();
}

饿汉式单例模式,当类加载的时候就直接实例化对象,因此不需要考虑线程安全问题。

  • 优点:实现简单,不需要考虑线程安全问题。
  • 缺点:不管有没有使用该对象实例,instance对象一直占用着这段内存。

懒汉与饿汉式如何选择?

  • 如果对象内存占用不大,且创建不复杂,直接使用饿汉的方式即可。
  • 其他情况均采用懒汉方式(优选)。

总结

文章会不定时更新,有时候一天多更新几篇,如果帮助您复习巩固了知识点,还请支持一下,后续会亿点点的更新!希望大家多多关注我们的其他内容!

(0)

相关推荐

  • 为何Java单例模式我只推荐两种

    双重检查模式 public class Singleton { private volatile static Singleton singleton; //1:volatile修饰 private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { //2:减少不要同步,优化性能 synchronized (Singleton.class) { // 3:同步,线程安全 if (sin

  • JAVA破坏单例模式的方式以及避免方法

    单例模式,大家恐怕再熟悉不过了,其作用与实现方式有多种,这里就不啰嗦了.但是,咱们在使用这些方式实现单例模式时,程序中就真的会只有一个实例吗? 聪明的你看到这样的问话,一定猜到了答案是NO.这里笔者就不卖关子了,开门见山吧!实际上,在有些场景下,如果程序处理不当,会无情地破坏掉单例模式,导致程序中出现多个实例对象. 下面笔者介绍笔者已知的三种破坏单例模式的方式以及避免方法. 1.反射对单例模式的破坏 我们先通过一个例子,来直观感受一下 (1)案例 DCL实现的单例模式: public class

  • 9种Java单例模式详解(推荐)

    单例模式的特点 一个类只允许产生一个实例化对象. 单例类构造方法私有化,不允许外部创建对象. 单例类向外提供静态方法,调用方法返回内部创建的实例化对象.  懒汉式(线程不安全) 其主要表现在单例类在外部需要创建实例化对象时再进行实例化,进而达到Lazy Loading 的效果. 通过静态方法 getSingleton() 和private 权限构造方法为创建一个实例化对象提供唯一的途径. 不足:未考虑到多线程的情况下可能会存在多个访问者同时访问,发生构造出多个对象的问题,所以在多线程下不可用这种

  • Java单例模式和多例模式实例分析

    本文实例讲述了Java单例模式和多例模式.分享给大家供大家参考,具体如下: 一 单例模式 1 代码 class Boss { private static Boss instance;// 静态成员变量,用来保存唯一创建的对象实例 private Boss () { // 利用私有化构造方法,阻止外部创建对象 } public static Boss findBoss() //检查并确保只有一个实例 { if (instance == null) { System.out.println("当前

  • Java双重检查加锁单例模式的详解

    什么是DCL DCL(Double-checked locking)被设计成支持延迟加载,当一个对象直到真正需要时才实例化: class SomeClass { private Resource resource = null; public Resource getResource() { if (resource == null) resource = new Resource(); return resource; } } 为什么需要推迟初始化?可能创建对象是一个昂贵的操作,有时在已知的运

  • java面试常见模式问题---单例模式

    1.简介 单例模式使⽤场景: 业务系统全局只需要⼀个对象实例,⽐如发号器. redis 连接对象等. Spring IOC容器中的 Bean 默认就是单例. Spring Boot 中的 Controller.Service.Dao 层中通过 @Autowire的依赖注⼊对象默认都是单例的. 单例模式分类: 懒汉:就是所谓的懒加载,延迟创建对象,需要用的时候再创建对象. 饿汉:与懒汉相反,提前创建对象. 单例模式实现步骤: 私有化构造函数. 提供获取单例的方法. 2.单例模式--懒汉式 单例模式

  • java面试常见模式问题---代理模式

    本篇总结的是 代理设计模式,后续会经常更新~ 代理模式最直观的解释就是,通过代理,将被代理对象 "增强"!(即,扩展被代理对象的功能) 代理模式分为静态代理,和动态代理:动态代理的代理类是动态生成的 , 静态代理的代理类是我们提前写好的逻辑. Java 中实现动态代理的方式有 2 种: JDK 动态代理 CGLIB 动态代理 1.静态代理 静态代理角色分析: 抽象角色 :一般使用接口或者抽象类来实现. 真实角色 :被代理的角色. 代理角色: 代理真实角色 , 代理真实角色后 ,一般会做

  • 教你java面试时如何聊单例模式

    目录 NO.1 单例模式的应用场景 NO.2 饿汉式单例 NO.3 懒汉式单例 NO.4 反射破坏单例 NO.5 序列化破坏单例 NO.6 注册式单例 NO.7 ThreadLocal 线程单例 总结 NO.1 单例模式的应用场景 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点.单例模式是创建型模式.单例模式在现实生活中应用也非常广泛.例如公司 CEO.部门经理等.在 J2EE 标准中,ServletContext.Servlet

  • Java多线程常见案例分析线程池与单例模式及阻塞队列

    目录 一.单例模式 1.饿汉模式 2.懒汉模式(单线程) 3.懒汉模式(多线程) 二.阻塞队列 阻塞队列的实现 生产者消费者模型 三.线程池 1.创建线程池的的方法 (1)ThreadPoolExecutor (2)Executors(快捷创建线程池的API) 2.线程池的工作流程 一.单例模式 设计模式:软件设计模式 是一套被反复使用.多数人知晓.经过分类编目.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性.程序的重用性. 单例模式:是设计模式的一种.

  • Java按值传递和按址传递(面试常见)

    先复制一个面试/笔试的题: 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 答案: 是值传递.Java语言的方法调用只支持参数的值传递.当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用.对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的.C++和C#中可以通过传引用或传输出参数来改变传入的参数的值.在C#中可以编写如下所示的代码,但是在Java中却做不到. java中的按值传

  • Java面试问题知识点总结

    本篇文章会对面试中常遇到的Java技术点进行全面深入的总结(阅读本文需要有一定的Java基础:若您初涉Java,可以通过这些问题建立起对Java初步的印象,待有了一定基础后再后过头来看收获会更大),喜欢的朋友可以参考下. 1. Java中的原始数据类型都有哪些,它们的大小及对应的封装类是什么? (1)boolean boolean数据类型非true即false.这个数据类型表示1 bit的信息,但是它的大小并没有精确定义. <Java虚拟机规范>中如是说:"虽然定义了boolean这

  • java面试常见问题之Hibernate总结

    主要从以下十几个方面对Hibernate做总结,包括Hibernate的检索方式,Hibernate中对象的状态,Hibernate的3种检索策略是什么,分别适用于哪种场合,ORM解决的不匹配问题, Hibernate映射继承关系的3种方式,Session的find()方法以及Query接口的区别等方面问题的总结,具体内容如下: 1  Hibernate的检索方式 Ø  导航对象图检索(根据已经加载的对象,导航到其他对象.) Ø  OID检索(按照对象的OID来检索对象.) Ø  HQL检索(使

  • 详解Java面试官最爱问的volatile关键字

    本文向大家分享的主要内容是Java面试中一个常见的知识点:volatile关键字.本文详细介绍了volatile关键字的方方面面,希望大家在阅读过本文之后,能完美解决volatile关键字的相关问题.  在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性都牵扯出来,深入地话还可以考察JVM底层实现以及操作系统的相关知识. 下面我们以一次假想的面试过

  • 基于Java 生产者消费者模式(详细分析)

    生产者消费者模式是多线程中最为常见的模式:生产者线程(一个或多个)生成面包放进篮子里(集合或数组),同时,消费者线程(一个或多个)从篮子里(集合或数组)取出面包消耗.虽然它们任务不同,但处理的资源是相同的,这体现的是一种线程间通信方式. 本文将先说明单生产者单消费者的情况,之后再说明多生产者多消费者模式的情况.还会分别使用wait()/nofity()/nofityAll()机制.lock()/unlock()机制实现这两种模式. 在开始介绍模式之前,先解释下wait().notify()和no

  • 23种设计模式(21)java享元模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述享元(Flyweight)模式的: Flyweight在拳击比赛中指最轻量级,即"蝇量级"或"雨量级",这里选择使用"享元模式"的意译,是因为这样更能反映模式的用意.享元模式是对象的结构模式.享元模式以共享的方式高效地支持大量的细粒度对象. Java中的String类型 在JAVA语言中,String类型就是使用了享元模式.String对象是final类型,对象一旦创建就不可改变.在J

随机推荐