首页 > 其他分享 >单例这种设计模式

单例这种设计模式

时间:2023-06-08 20:02:33浏览次数:34  
标签:设计模式 这种 实例 private SingleInstance static 单例 sInstance


随着我们编写代码的深入,我们或多或少都会接触到设计模式,其中单例(Singleton)模式应该是我们耳熟能详的一种模式。本文将比较特别的介绍一下Java设计模式中的单例模式。

概念

单例模式,又称单件模式或者单子模式,指的是一个类只有一个实例,并且提供一个全局访问点。

实现思路

  • 在单例的类中设置一个private静态变量sInstance,sInstance类型为当前类,用来持有单例唯一的实例。
  • 将(无参数)构造器设置为private,避免外部使用new构造多个实例。
  • 提供一个public的静态方法,如getInstance,用来返回该类的唯一实例sInstance。

其中上面的单例的实例可以有以下几种创建形式,每一种实现都需要保证实例的唯一性。

饿汉式

饿汉式指的是单例的实例在类装载时进行创建。如果单例类的构造方法中没有包含过多的操作处理,饿汉式其实是可以接受的。

饿汉式的常见代码如下,当SingleInstance类加载时会执行private static SingleInstance sInstance = new SingleInstance();初始化了唯一的实例,然后getInstance()直接返回sInstance即可。



public class SingleInstance {
  private static SingleInstance sInstance = new SingleInstance();
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      return sInstance;
  }
}



饿汉式的问题

  • 如果构造方法中存在过多的处理,会导致加载这个类时比较慢,可能引起性能问题。
  • 如果使用饿汉式的话,只进行了类的装载,并没有实质的调用,会造成资源的浪费。

懒汉式

懒汉式指的是单例实例在第一次使用时进行创建。这种情况下避免了上面饿汉式可能遇到的问题。

但是考虑到多线程的并发操作,我们不能简简单单得像下面代码实现。


public class SingleInstance {
  private static SingleInstance sInstance;
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      if (null == sInstance) {
          sInstance = new SingleInstance();
      }
      return sInstance;
  }
}



上述的代码在多个线程密集调用getInstance时,存在创建多个实例的可能。比如线程A进入null == sInstance这段代码块,而在A线程未创建完成实例时,如果线程B也进入了该代码块,必然会造成两个实例的产生。

synchronized修饰方法

使用synchrnozed修饰getInstance方法可能是最简单的一个保证多线程保证单例唯一性的方法。
synchronized修饰的方法后,当某个线程进入调用这个方法,该线程只有当其他线程离开当前方法后才会进入该方法。所以可以保证getInstance在任何时候只有一个线程进入。



public class SingleInstance {
  private static SingleInstance sInstance;
  private SingleInstance() {
  }
  public static synchronized SingleInstance getInstance() {
      if (null == sInstance) {
          sInstance = new SingleInstance();
      }
      return sInstance;
  }
}



但是使用synchronized修饰getInstance方法后必然会导致性能下降,而且getInstance是一个被频繁调用的方法。虽然这种方法能解决问题,但是不推荐。

双重检查加锁

使用双重检查加锁,首先进入该方法时进行null == sInstance检查,如果第一次检查通过,即没有实例创建,则进入synchronized控制的同步块,并再次检查实例是否创建,如果仍未创建,则创建该实例。

双重检查加锁保证了多线程下只创建一个实例,并且加锁代码块只在实例创建的之前进行同步。如果实例已经创建后,进入该方法,则不会执行到同步块的代码。



public class SingleInstance {
  private static volatile SingleInstance sInstance;
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      if (null == sInstance) {
          synchronized (SingleInstance.class) {
              if (null == sInstance) {
                  sInstance = new SingleInstance();
              }
          }
      }
      return sInstance;
  }
}



volatile是什么

Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。使用volatile修饰sInstance变量之后,可以确保多个线程之间正确处理sInstance变量。
关于volatile,可以访问深入分析Volatile的实现原理了解更多。

利用static机制

在Java中,类的静态初始化会在类被加载时触发,我们利用这个原理,可以实现利用这一特性,结合内部类,可以实现如下的代码,进行懒汉式创建实例。



public class SingleInstance {
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      return SingleInstanceHolder.sInstance;
  }
  private static class SingleInstanceHolder {
      private static SingleInstance sInstance = new SingleInstance();
  }
}



关于这种机制,可以具体了解双重检查锁定与延迟初始化

好奇问题

真的只有一个对象么

其实,单例模式并不能保证实例的唯一性,只要我们想办法的话,还是可以打破这种唯一性的。以下几种方法都能实现。

  • 使用反射,虽然构造器为非公开,但是在反射面前就不起作用了。
  • 如果单例的类实现了cloneable,那么还是可以拷贝出多个实例的。
  • Java中的对象序列化也有可能导致创建多个实例。避免使用readObject方法。
  • 使用多个类加载器加载单例类,也会导致创建多个实例并存的问题。

单例可以继承么

单例类能否被继承需要分情况而定。

可以继承的情况

当子类是父类单例类的内部类时,继承是可以的。



public class BaseSingleton {
  private static volatile BaseSingleton sInstance;
  private BaseSingleton() {
  }
  public static BaseSingleton getInstance() {
      if (null == sInstance) {
          synchronized(BaseSingleton.class) {
              if (null == sInstance) {
                  sInstance = new BaseSingleton();
              }
          }
      }
      return sInstance;
  }
  public static class  MySingleton extends BaseSingleton {
  }
}



但是上面仅仅是编译和执行上允许的,但是继承单例没有实际的意义,反而会变得更加事倍功半,其代价要大于新写一个单例类。感兴趣的童鞋可以尝试折腾一下。

不可以继承的情况

如果子类为单独的类,非单例类的内部类的话,那么在编译时就会出错Implicit super constructor BaseSingleton() is not visible for default constructor. Must define an explicit constructor,主要原因是单例类的构造器是private,解决方法是讲构造器设置为可见,但是这样做就无法保证单例的唯一性。所以这种方式不可以继承。

总的来说,单例类不要继承。

单例 vs static变量

全局静态变量也可以实现单例的效果,但是使用全局变量无法保证只创建一个实例,而且使用全局变量的形式,需要团队的约束,执行起来可能会出现问题。

关于GC

因为单例类中又一个静态的变量持有单例的实例,所以相比普通的对象,单例的对象更不容易被GC回收掉。单例对象的回收应该发生在其类加载器被GC回收掉之后,一般不容易出现。

标签:设计模式,这种,实例,private,SingleInstance,static,单例,sInstance
From: https://blog.51cto.com/u_16131764/6442772

相关文章

  • Static 单例中 QNetworkAccessManager 的释放问题
    结论:QNetworkAccessManager以Static对象作为父对象时,需要注意在main函数return前释放,否则可能导致Qt框架内部的重复释放引发崩溃。原因:Static对象是在Main函数return后才释放的,此时,已经QApplication已失效 参考https://bugreports.qt.io/browse/QTBUG-82984htt......
  • Java中枚举类的特殊用法-使用枚举实现单例模式和策略模式
    上面针对枚举类实现单例模式和策略模式有所涉及,下面补充。Java中使用枚举类实现单例模式为什么可以这样做?枚举类不能new,因此保证单例枚举类不能被继承类不加载时,不会实例化使用枚举类创建的单例还有一个好处,就是即使使用反射,也无法打破它的单例性质新建枚举类publicenumSingleEn......
  • 使用c#实现23种设计模式
    使用c#实现23种常见的设计模式设计模式通常分为三个主要类别:创建型模式结构型模式行为型模式。这些模式是用于解决常见的对象导向设计问题的最佳实践。以下是23种常见的设计模式并且提供c#代码案例:创建型模式:1.单例模式(Singleton)publicsealedclassSingleton......
  • 新项目,不妨采用这种架构分层,很优雅!
    大家好,我是飘渺。今天继续更新DDD&微服务的系列文章。在专栏开篇提到过DDD(Domain-DrivenDesign,领域驱动设计)学习起来较为复杂,一方面因为其自身涉及的概念颇多,另一方面,我们往往缺乏实战经验和明确的代码模型指导。今天,我们将专注于DDD的分层架构和实体模型,期望为大家落地DDD提供......
  • 使用单例获取yml配置的接口List,来为过滤器设置白名单
    最近在涉及登录访问接口的问题,服务器被人给攻击了,考虑给接口加白名单,如果用注解的方式还要跑到接口上去加代码也不够灵活,那么如何使用过滤器来实现呢?让我们来用demo实现看看。第一步、首先新建一个demo项目jdk11新建一个springboot项目,配置pom.xml1<?xmlversion="1.0"e......
  • Python设计模式-01工厂模式
    工厂模式工厂模式(FactoryPattern)是一种创建型设计模式,它提供了一种创建对象的最佳方式,而无需指定将要创建的对象的确切类。工厂模式通过定义一个工厂接口来创建对象,让子类决定实例化哪个类。这样可以将对象的创建与使用分离,从而降低系统的耦合度。工厂模式包含三种角色:具体工......
  • Java设计模式-适配器模式
    适配器模式(AdapterPattern)是一种常见的设计模式,它主要用于在不改变现有系统结构的情况下,将一个类的接口转换成客户端所期望的另一个接口。在本文中,我们将介绍适配器模式的基本概念、实现方法以及优缺点,并探讨适配器模式在Java编程语言中的具体应用。简介适配器模式是一种结构型......
  • 转:使用c#实现23种常见的设计模式
    转自:https://www.cnblogs.com/hejiale010426/archive/2023/06/05/17457761.html设计模式通常分为三个主要类别:创建型模式结构型模式行为型模式这些模式是用于解决常见的对象导向设计问题的最佳实践。以下是23种常见的设计模式并且提供c#代码案例:1.创建型模式1.1单例模......
  • 小话设计模式
    设计模式1关系......
  • 单例模式
    单例模式单例模式(SingletonPattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不......