解锁设计模式的神秘面纱:编写无懈可击的代码之单例设计模式

简介: 解锁设计模式的神秘面纱:编写无懈可击的代码之单例设计模式

前言


单例设计模式是23种设计模式中最常用的设计模式之一,无论是三方类库还是日常开发几乎都有单例设

计模式的影子。单例设计模式提供了一种在多线程情况下保证实例唯一性的解决方案。单例设计模式虽然简单,但是实现方案却非常多,大体上有以下7种最常见的方式。

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗



饿汉模式


所谓饿汉式,就是不管你用不用这个对象,都先把这个对象进行创建出来,这样子在使用的时候就可以保证是单例。

特点


  • 线程安全性 在加载的时候已经被实例化,所以只有这一次,线程安全的
  • 懒加载 没有延迟加载,好长时间不使用,影响性能

示例:

// 没有延迟加载,好长时间不使用,影响性能
public class test1 {
  /**
   * 直接初始化对象
   * */
  private static final test1 INSTANCE = new test1();
  /**
   * 不允许外界进行new对象
   **/
  private test1() {
  }
  /**
   * 放行唯一方法 获取对象
   * @return
   */
  public static test1 getInstance() {
    return INSTANCE;
  }
}

总结: 这种方案实现起来最简单,当test1被加载后,就会立即创建instance,因此该方法可以保证百分百的单例,instance不可能被实例化两次。但是这种做instance可能被加载后很长一段时间才会被使用,就意味着instance开辟的内存占用时间更多。

注意:

如果一个类中成员属性比较少,且占用内存资源不多,那么就可以使用饿汉式。如果一个类中都是比较重的资源,这种方式就比较不妥




懒汉模式


所谓懒汉式就是在使用时再去创建,可以理解成懒加载。

示例:

public class test2 {
  private static test2 instance;
  private test2() {
    System.out.println("类被实例化了");
  }
  public static test2 getInstance() {
    if (instance == null) {
      instance = new test2();
    }
    return instance;
  }
}

总结: 当instance为null时,getInstance会首先去new一个实例,那之后再将实例返回。

注意: 但是这种实现方式会存在线程安全问题,多个线程同时获取将会出现不同的对象实例,破坏了单例的原则。





懒汉模式+同步方法


为了解决懒汉式线程安全问题,我们可以加上同步方法


特点

  • 直接在方法上进行加锁
  • 锁的力度太大. 性能不是太好
  • synchronized  退化到了串行执行

示例:

public class test2 {
  private static test2 instance;
  private test2() {
    System.out.println("类被实例化了");
  }
  public static synchronized test2 getInstance() {
    if (instance == null) {
      instance = new test2();
    }
    return instance;
  }
}

总结: 这种做法就保证了懒加载又能够百分百保证instance是单例的,但是synchronized关键字天生的排他性导致该方法性能过低。




双重检查锁


Double-Check-Locking是一种比较聪明的做法,我们其实只需要在instance为null时,保证线程的同步性,让只有一个线程去创建对象即可,而其他线程依然是直接使用,而当instance已经有实例之后,我们并不需要线程同步操作,直接并行读即可,这里我们再给类里面加上两个属性.


特点

  • 保证了线程安全
  • 如果实例中存在多个成员属性. 由于在代码执行过程当中,会对代码进行重排,,重排后, 可能导致别一个线程获取对象时初始化属性不正确的情况
  • 加volatile
  • 创建对象步骤
  • memory = allocate();   //1:分配对象的内存空间 ctorInstance(memory);  //2:初始化对象 instance = memory;     //3:设置instance指向刚分配的内存地址
  • memory = allocate();   //1:分配对象的内存空间 instance = memory;     //3:设置instance指向刚分配的内存地址 //注意,此时对象还没有被初始化! ctorInstance(memory);  //2:初始化对象
  • 重排问题


示例:

public class test2 {
  private static test2 instance;
  private Object o1;
  private Object o2;
  private test2() {
    o1=new Object();
    o2=new Object();
    System.out.println("类被实例化了");
        }
  public static test2 getInstance() {
    // 为null时,进入同步代码块,同时避免了每次都需要进入同步代码块
    if (instance == null) {
      // 只有一个线程能够获取到锁
      synchronized (test2.class) {
        // 如果为Null在创建
        if (instance == null) {
          instance = new test2();
                    }
                }
          }
    return instance;
      }
}

总结: 当两个线程发现 instance == null 时,只有一个线程有资格进入同步代码块,完成对instance的初始化,随后的线程再次进入同步代码块之后,因为 instance == null 不成立,就不会再次创建,这是未加载情况下并行的场景,而instance加载完成后,再有线程进入getInstance方法后,就直接返回

instance,不会进入到同步代码块,从而提高性能。

注意: 这种做法看似完美和巧妙,既满足懒加载,又保证instance的唯一性,但是这种方式实际上是会出现空指针异常的。




解析空指针异常的问题:


在test2构造方法中,我们会初始化 o1 和 o2两个资源,还有Single自身,而这三者实际上并无前后关系的约束,那么极有可能JVM会对其进行重排序,导致先实例化test2,再实例化o1和o2,这样在使用test2时,可能会因为o1和o2没有实例化完毕,导致空指针异常。




双重检查锁+volatile


解决上面的方法其实很简单,给instance加上一个volatile关键字即可,这样就防止了重排序导致的程序异常。


private volatile static test2 instance;


内部类(Holder)方式

holder方式借助了类加载的特点,我们直接看代码。

public class test3 {
  private test3() {
    System.out.println("类被实例化了");
  }
  /**
   * 使用内部类方式不会主动加载,只有主类被使用的时候才会进行加载
     * 第一次使用到的时候才去执行  只执行一次
   */
  private static class Holder {
    private static test3 instance = new test3();
  }
  /**
   * 提供外界进行调用
   * @return
   */
  public static test3 getInstance() {
    return Holder.instance;
  }
}


特点

  • 它结合了饿汉模式 安全性,也结合了懒汉模式懒加载。不会使用synchronized 所以性能也有所保证
  • 声明类的时候,成员变量中不声明实例变量,而放到内部静态类中
  • 不存在线程安全问题
  • 懒加载的
  • 反序列化问题 // 该方法在反序列化时会被调用 protected Object readResolve() throws ObjectStreamException { System.out.println("调用了readResolve方法!"); return Hoder.instance; }

总结: 我们发现,在test3中并没有instance,而是将其放到了静态内部类中,使用饿汉式进行加载。但是实际上这并不是饿汉式。因为静态内部类不会主动加载,只有主类被使用时才会加载,这也就保证了程序运行时并不会直接创建一个instance而浪费内存,当我们主动引用Holder时,才会创建instance实例,从而保证了懒加载。





枚举方式

枚举的方式实现单例模式是《Effective Java》作者力推的方式,枚举类型不允许被继承,同样是线程安全的并且只能被初始化一次。但是使用枚举类型不能懒加载,比如下面的代码,一旦使用到里面的静态方法,INSTANCE就会立即被实例化。


特点


  • 不存在线程安全
  • 没有懒加载

示例:

public enum test4 {
  INSTANCE;
  test4() {
    System.out.println("类被实例化了");
  }
  public static test4 getInstance() {
      return INSTANCE;
  }
}

源码

  • Runtime类
  • Mybatis ErrorContext
  • 类加载器





最后


本期结束咱们下次再见👋~

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

相关文章
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
12天前
|
设计模式 算法 数据库连接
PHP中的设计模式:提高代码的可维护性和扩展性
【10月更文挑战第13天】 本文将探讨PHP中常见的设计模式及其在实际项目中的应用。通过对比传统编程方式,我们将展示设计模式如何有效地提高代码的可维护性和扩展性。无论是单例模式确保类的单一实例,还是观察者模式实现对象间的松耦合,每一种设计模式都为开发者提供了解决特定问题的最佳实践。阅读本文后,读者将能更好地理解和应用这些设计模式,从而提升PHP编程的效率和质量。
|
27天前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP开发领域,设计模式是解决常见问题的高效方案集合。它们不是具体的代码,而是一种编码和设计经验的总结。单例模式作为设计模式中的一种,确保了一个类仅有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的基本概念、实现方式及其在PHP中的应用。
单例模式在PHP中的应用广泛,尤其在处理数据库连接、日志记录等场景时,能显著提高资源利用率和执行效率。本文从单例模式的定义出发,详细解释了其在PHP中的不同实现方法,并探讨了使用单例模式的优势与注意事项。通过对示例代码的分析,读者将能够理解如何在PHP项目中有效应用单例模式。
|
2月前
|
设计模式 算法 数据库连接
PHP中的设计模式:提高代码的可维护性与扩展性
设计模式在PHP开发中至关重要,如单例模式确保类仅有一个实例并提供全局访问点,适用于管理数据库连接或日志记录。工厂模式封装对象创建过程,降低系统耦合度;策略模式定义算法系列并使其可互换,便于实现不同算法间的切换。合理选择设计模式需基于需求分析,考虑系统架构,并通过测试驱动开发验证有效性,确保团队协作一致性和代码持续优化。设计模式能显著提升代码质量,解决开发中的设计难题。
32 8
|
2月前
|
设计模式 算法 PHP
PHP中的设计模式:提升代码的灵活性与可维护性
在本文中,我们将深入探讨PHP编程语言中的一种重要概念——设计模式。设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它代表了最佳的实践,被有经验的面向对象的软件开发人员所采用。本文将通过具体的实例,展示如何在PHP项目中应用设计模式,以提高代码的灵活性和可维护性。无论你是PHP初学者还是经验丰富的开发者,都能从中获得有价值的见解。
|
2月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入探索与实践在软件开发的广袤天地中,PHP以其独特的魅力和强大的功能,成为无数开发者手中的得力工具。而在这条充满挑战与机遇的征途上,设计模式犹如一盏明灯,指引着我们穿越代码的迷雾,编写出更加高效、灵活且易于维护的程序。今天,就让我们聚焦于设计模式中的璀璨明珠——策略模式,深入探讨其在PHP中的实现方法及其实际应用价值。
策略模式,这一设计模式的核心在于它为软件设计带来了一种全新的视角和方法。它允许我们在运行时根据不同情况选择最适合的解决方案,从而极大地提高了程序的灵活性和可扩展性。在PHP这门广泛应用的编程语言中,策略模式同样大放异彩,为开发者们提供了丰富的创作空间。本文将从策略模式的基本概念入手,逐步深入到PHP中的实现细节,并通过一个具体的实例来展示其在实际项目中的应用效果。我们还将探讨策略模式的优势以及在实际应用中可能遇到的挑战和解决方案,为PHP开发者提供一份宝贵的参考。
|
2月前
|
设计模式 存储 数据库连接
探索PHP中的设计模式:提高代码的可维护性与扩展性
本文将深入探讨PHP中常用的设计模式,包括单例模式、工厂模式和观察者模式。通过具体的代码示例,展示如何在实际项目中应用这些设计模式,以提高代码的可维护性与扩展性。无论你是PHP初学者还是有一定经验的开发者,都可以通过本文的学习,提升你的编程技巧和项目架构能力。
|
2月前
|
设计模式 算法 PHP
PHP中的设计模式:提升代码的可维护性与扩展性
在软件开发中,设计模式是一套经过验证的解决特定问题的模板。对于使用PHP语言的开发者来说,合理运用设计模式不仅可以优化代码结构,还能显著提高项目的可维护性和扩展性。本文将探讨几种常见的设计模式及其在PHP中的应用,帮助开发者编写更高效、更灵活的代码。
35 2
|
2月前
|
设计模式 算法 搜索推荐
PHP中的设计模式:提高代码可维护性的秘诀
在本文中,我们将探讨PHP设计模式的重要性以及它们如何帮助开发者编写出更加灵活、可维护的代码。我们将介绍几种常见的设计模式,包括单例模式、工厂模式和策略模式,并通过实际示例展示它们在PHP中的应用。最后,我们会讨论何时使用这些设计模式以及在实际项目开发中的最佳实践。
36 1
|
2月前
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。

热门文章

最新文章