设计模式 Singleton

本文探讨了单例模式的几种实现方式,包括通用泛型单例模式及其线程安全处理方法。介绍了如何通过锁机制和嵌套类静态构造来确保多线程环境下单例模式的正确性。
uploads/200703/14_222942_singleton.gif


类别:创建型
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用:
  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
----------------------

单件和工厂估计是最常用的设计模式了,想必每个人都很熟。本贴只是研究一些 Singleton 的"变种"。

1. 通用泛型单件模式
class ClassA
{
}

class ClassB
{
}

public static class Singleton<T>
  where T : class, new()
{
  private static T instance;

  public static T Instance
  {
    get
    {
      if (instance == null)
        instance = new T();

      return instance;
    }
  }
}

public class Program
{
  static void Main(string[] args)
  {
    ClassA a1 = Singleton<ClassA>.Instance;
    ClassA a2 = Singleton<ClassA>.Instance;
    Console.WriteLine(object.ReferenceEquals(a1, a2)); // output: true

    ClassB b1 = Singleton<ClassB>.Instance;
    Console.WriteLine(object.ReferenceEquals(a1, b1)); // output: false
  }
}

该方式的好处是:
(1) 支持已有类型
(2) 支持多类型
(3) 无需修改目标类型,用户可以在单件和多实例之间自由选择。

2. 线程安全

大多数单件模式的例子(包括上面的)并没有做线程安全处理,因此在执行 "new Instance()" 时存在一定的隐患。

方式1:Lock
public static class Singleton<T>
  where T : class, new()
{
  private static T instance;
  private static object o = new object();

  public static T Instance
  {
    get
    {
      if (instance == null)
      {
        lock (o)
        {
          if (instance == null)
            instance = new T();
        }
      }

      return instance;
    }
  }
}

注意使用了两次 "instance == null" 判断。因为可能有两个以上的线程进入第一个判断语句,其中一些在 lock 释放后有可能再次执行 "new T()" 操作,因此加上第二个判断避免这种情况发生。

方式2:嵌套类静态构造
public static class Singleton<T>
  where T : class, new()
{
  public static T Instance
  {
    get
    {
      return Nested.instance;
    }
  }

  private class Nested
  {
    internal static T instance = new T();
  }
}

我们知道无论如何,静态构造只会被执行一次,因此自然也就是多线程安全的了。只所以使用嵌套类是因为在需要的时候才初始化该实例,另外不用对外公开这些细节。

另外,需要补充说明的是:

泛型类声明中的静态变量在相同封闭构造类型的所有实例之间共享,但是不会在不同封闭构造类型的实例之间共享。不管静态变量的类型是否涉及任何类型参数,这些规则都适用。
### 单例设计模式Singleton Pattern)的概念 单例模式是一种常用的软件设计模式,其核心思想是确保一个类只有一个实例存在,并提供一个全局访问点来访问该实例。这种模式在系统中需要协调全局行为时非常有用,例如管理配置信息、日志记录、线程池等场景。通过单例模式,可以避免多个实例造成的资源浪费或状态不一致问题[^1]。 ### 单例设计模式的用途 单例模式的主要用途包括: 1. **统一资源管理**:例如服务器的配置信息通常由一个单例对象读取,其他对象通过该对象获取配置信息,简化了复杂环境下的配置管理[^1]。 2. **全局访问点**:确保某个类的实例在整个应用程序中只有一个,并且可以被全局访问。 3. **资源共享**:如数据库连接池、线程池等,避免重复创建和销毁资源带来的性能开销。 ### 单例设计模式的实现方式 单例模式的实现通常需要满足以下几个关键点: 1. **私有化构造函数**:防止外部直接通过 `new` 关键字创建实例。 2. **静态变量保存实例**:在类内部定义一个静态变量用于保存类的唯一实例。 3. **提供全局访问方法**:通过静态方法返回类的实例。 #### 1. 饿汉式(Eager Initialization) 饿汉式在类加载时就创建实例,因此线程安全。适用于实例创建成本不高且需要立即加载的场景。 ```java public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } ``` 这种方式的优点是简单且线程安全,但缺点是无论是否使用该实例,都会在类加载时创建,可能造成资源浪费[^2]。 #### 2. 懒汉式(Lazy Initialization) 懒汉式在第一次调用 `getInstance()` 时才创建实例,适用于实例创建成本较高的场景。 ```java public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 这种方式虽然节省了资源,但在多线程环境下可能会导致多个实例被创建,因此需要额外的同步机制来保证线程安全[^2]。 #### 3. 双重校验锁(Double-Checked Locking) 双重校验锁是一种优化的懒汉式实现,通过 `synchronized` 关键字和 `volatile` 关键字确保线程安全并减少同步开销。 ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 这种方式在多线程环境下能够高效地保证单例的唯一性,同时避免了不必要的同步开销[^2]。 #### 4. 静态内部类(Static Nested Class) 静态内部类利用了类加载机制来保证线程安全,同时实现了懒加载。 ```java public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } ``` 这种方式在调用 `getInstance()` 时才会加载内部类并创建实例,因此既保证了线程安全,又避免了不必要的资源浪费[^2]。 #### 5. 枚举(Enum) 枚举是实现单例模式的一种简洁且线程安全的方式,尤其适合需要序列化或反序列化的场景。 ```java public enum Singleton { INSTANCE; public void doSomething() { // 业务逻辑 } } ``` 枚举实现的单例模式天然支持序列化和反序列化,并且可以避免反射攻击,是一种推荐的实现方式。 #### 6. JavaScript 中的实现 在 JavaScript 中,可以通过闭包或类来实现单例模式。 **闭包实现**: ```javascript let Singleton = function (name) { this.name = name; }; Singleton.getInstance = (function () { let instance = null; return function (name) { if (!instance) { instance = new Singleton(name); } return instance; }; })(); ``` **类实现(ES6)**: ```javascript class Singleton { constructor(name) { if (!Singleton.instance) { this.name = name; Singleton.instance = this; } return Singleton.instance; } } const s1 = new Singleton('A'); const s2 = new Singleton('B'); console.log(s1 === s2); // true ``` 这些实现方式在 JavaScript 中能够确保全局只有一个实例,并且可以通过闭包或类的方式实现懒加载和线程安全[^3]。 ### 单例模式的优缺点 **优点**: - 提供了对唯一实例的受控访问。 - 节省系统资源,避免重复创建和销毁实例。 - 可以全局共享状态。 **缺点**: - 违反了单一职责原则,单例类可能承担过多职责。 - 难以进行单元测试,因为单例的全局状态可能导致测试之间的依赖。 - 在分布式系统中,单例模式可能难以扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值