单例模式是结构最简单的设计模式之一,也是最普遍应用的设计模式之一。

简单但是重要,唯一所以特别。 

上篇:

【学习设计模式5】创建型模式三结义—抽象工厂模式_Aiky哇-CSDN博客抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形式https://aikysay.blog.csdn.net/article/details/122691096

下篇:

【学习设计模式7】复制粘贴就得了——原型模式_Aiky哇的博客-CSDN博客那么有没有一种方法可以名正言顺的复制粘贴呢?当然有了,那就是原型模式。原型模式也叫克隆模式。https://aikysay.blog.csdn.net/article/details/123407658 

单例模式的定义

单例模式可以简单理解为,存在一个类定义,有且仅有一个实例。

单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。 

单例模式的实现模式

单例模式的实现方式,传统上有“懒汉式”和“饿汉式”两种。

  • 懒汉式:需要的时候才创建,在这种写法中,需要注意的是要注意并发调用创建函数的场景,需要用锁或其他并发控制原语来控制实例只会创建一次。
  • 饿汉式:事先创建好,需要时直接返回。

下面使用go语言来简单地写一下两种实现方法。

type singleton struct {
    //...
    //成员变量
}

// 懒汉式------------------------------------------
var instance *singleton
var once sync.Once

func GetInstance() *singleton {
	once.Do(func() { // once保证函数只运行一次
		instance = &singleton{
            //...
            //为成员变量赋值
        }
	})
	return instance
}

//饿汉式------------------------------------------
var instance *singleton = &singleton{
    //...
    //为成员变量赋值
}

func GetInstance() *singleton {
    return instance
}

两种单例模式的创建方法各有优劣。

饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。

懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理好多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。

那么有没有一种实现方法,能够既像饿汉式一样并发时不需要判断,又能像懒汉式一样延迟创建不占用资源呢?

有的,这种实现方法叫IoDH(Initialization Demand Holder)。可以实现延迟加载,又可以保证线程安全,不影响系统性能。

//Initialization on Demand Holder
class Singleton {
	private Singleton() {
	}
	
	private static class HolderClass {
            private final static Singleton instance = new Singleton();
	}
	
	public static Singleton getInstance() {
	    return HolderClass.instance;
	}
	
	public static void main(String args[]) {
	    Singleton s1, s2; 
        s1 = Singleton.getInstance();
	    s2 = Singleton.getInstance();
	    System.out.println(s1==s2);
	}
}

这种方式的实现依赖于JVM对类加载过程中初始化阶段的执行。

所以并不是所有语言都能够通过这种方式实现单例模式,java可以的,但是go就不行,虽然go的sync.Once也能延迟执行,但是每次还是有一次bool类型的判断。

单例模式的适用场景

在以下情况下可以考虑使用单例模式:

  1. 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  2. 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

常见的例子,windows的任务管理器,鼠标指针等等。

单例模式其实还可以引申一下,变成多例模式。

这时候和单例模式大同小异,只不过多例中每个例子都通过单例模式的方法来实现。

单例模式的优缺点

主要优点

  1. 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
  2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
  3. 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。
     

主要缺点

  1. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  3. 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

参考文献

确保对象的唯一性——单例模式 (三)_刘伟技术博客-CSDN博客

单例模式golang实现 - 知乎

Go的单例模式 - 醉清风JM - 博客园

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐