设计模式--单例模式

简介:

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

上面是引用维基百科上的定义。                                                                      

 

既然单例模式只允许存在一个对象,那么对象的拷贝,赋值就都是不允许的,因此把拷贝构造函数、赋值操作符全部声明为private

 

第一种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class  Singleton
{
  private :
      static  Singleton *m_instance;
      Singleton(){} //隐藏构造函数
      Singleton( const  Singleton &){} //隐藏拷贝构造函数
      Singleton& operator=( const  Singleton &a){} //隐藏赋值操作符
      ~Singleton(){} //隐藏析构函数
  public :
     static  Singleton *getInstance()
     {
         if (m_instance == NULL)
             m_instance = new  Singleton;
         return  m_instance;
     }
     
};
Singleton * Singleton::m_instance = NULL;

 

这种实现很明显满足单例模式的要求,但是有两个问题 (1)我们new的对象没有被delete,(2)这种设计不是线程安全的(两个线程可能同时判断m_instance == NULL,这样就有两个实例被创建了),为了解决上面的问题,第二种实现如下


第二种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class  Singleton
{
  private :
      static  Singleton *m_instance;
      Singleton(){} //隐藏构造函数
      Singleton( const  Singleton &){} //隐藏拷贝构造函数
      Singleton& operator=( const  Singleton &a){} //隐藏赋值操作符
      ~Singleton(){} //隐藏析构函数
      class  DelInstance
      {
      public :
         ~DelInstance()
         {
             if (Singleton::m_instance)
                 delete  Singleton::m_instance;
         }
      };
      static  DelInstance delIns; //负责回收new出来的对象;
 
  public :
     static  Singleton *getInstance()
     {
         if (m_instance == NULL)
         {
             lock(); //加锁(lock 和 unlock是随便写的函数,c++本身没有)
             if (m_instance == NULL)
                 m_instance = new  Singleton;
             unlock(); //释放锁
         }
         return  m_instance;
     }
 
};
Singleton::DelInstance Singleton::delIns;
Singleton * Singleton::m_instance = NULL;

 

这里我们使用了另一个私有内嵌类DelInstance,在Singleton内定义了一个静态的的对象delIns来负责回收new出来的对象,因为静态对象delIns在程序结束时会自动调用自己析构函数从而释放m_instance指向的内存,这里新手可能会犯两个错误:

1、不使用额外的类,直接把delete语句写在singleton的析构函数中。这种做法是错误的,因为我们是通过new出来的singleton实例,程序结束时不会自动调用析构函数,再者如果真的调用了就会进入一个无限循环的状态,即singleton的析构函数是为了删除一个singleton对象,删除该对象时,又会调用singleton的析构函数,这样一直循环下去。

2、delIns成员不定义成static。这也是错误的,如果delIns不是static,那么delIns就要等到singleton对象析构时才会析构,但是delIns的目的就是要析构singleton,这就矛盾了。如果delIns是static的,他的生命期和他所在的类对象没有关系,他相当于是全局的,当他的生命期到的时候就会自动析构,从而析构singleton。

getInstance中我们使用了double-check来保证线程安全,只有当m_instance是NULL时,线程才会加锁。这样也保证了只创建了一个对象实例。

 

这是一种懒汉模式,即等到需要时才创建对象的实例。                                                                                 本文地址


第三种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class  Singleton
{
  private :
      static  Singleton *m_instance;
      Singleton(){} //隐藏构造函数
      Singleton( const  Singleton &){} //隐藏拷贝构造函数
      Singleton& operator=( const  Singleton &a){} //隐藏赋值操作符
      ~Singleton(){} //隐藏析构函数
      class  DelInstance
      {
      public :
         ~DelInstance()
         {
             if (Singleton::m_instance)
                 delete  Singleton::m_instance;
         }
      };
      static  DelInstance delIns; //负责回收new出来的对象;
 
  public :
     static  Singleton *getInstance()
     {
         return  m_instance;
     }
 
};
Singleton::DelInstance Singleton::delIns;
Singleton * Singleton::m_instance = new  Singleton;

 

这是属于饿汉模式,即一开始就创建一个类的实例,以后都返回其地址,是线程安全的。


第四种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class  Singleton
{
  private :
      static  Singleton s;
      Singleton(){} //隐藏构造函数
      Singleton( const  Singleton &){} //隐藏拷贝构造函数
      Singleton& operator=( const  Singleton &a){} //隐藏赋值操作符
      ~Singleton(){} //隐藏析构函数
  public :
     static  Singleton *getInstance()
     {
         return  &s;
     }
     
};
 
Singleton Singleton::s;

 

饿汉模式,这种实现定义一个私有的静态对象实例,(注意不能在类中定义自身的非静态对象,因为这样会形成无限循环定义,而静态对象因为保证只有一个副本,因此不会循环定义),这也是线程安全的


第五种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class  Singleton
{
  private :
      Singleton(){} //隐藏构造函数
      Singleton( const  Singleton &){} //隐藏拷贝构造函数
      Singleton& operator=( const  Singleton &a){} //隐藏赋值操作符
      ~Singleton(){} //隐藏析构函数
  public :
     static  Singleton *getInstance()
     {
         lock(); //c++11 可以不用加锁
         static  Singleton s;
         unlock();
         return  &s;
     }
 
};

 

懒汉模式,这种实现只有当第一次调用getInstance时定义局部静态变量s,以后直接返回。c++11之前需要加锁,因为变量的初始化操作不是原子操作,不加锁就不是线程安全的的;c++11则不用加锁,因为c++11保证:如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待完成该变量完成初始化(见here)。


注意

 

对于第一、二、三、四种方式,都有可能导致static initialization order fiasco (可参考here)。因为c++中,在全局或名字空间范围内的全局变量,在一个类中被声明为static,或,在一个文件范围被定义为static。这三种变量统称“非局部静态对象”,这三种对象的初始化顺序“C++”未作明确规定。因此如果有个类在构造函数中调用了getInstance:

struct Foo {
    Foo() {
        Singleton::getInstance();
    }
  };
  Foo foo;
不能保证foo初始化时,m_instance已经初始化





本文转自tenos博客园博客,原文链接:http://www.cnblogs.com/TenosDoIt/p/3639395.html,如需转载请自行联系原作者

目录
相关文章
|
5月前
|
设计模式 缓存 安全
【设计模式】【创建型模式】单例模式(Singleton)
一、入门 什么是单例模式? 单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。它常用于需要全局唯一对象的场景,如配置管理、连接池等。 为什么要单例模式? 节省资源 场景:某些对象创
165 15
|
12月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
91 2
|
6月前
|
设计模式 存储 安全
设计模式-单例模式练习
单例模式是Java设计模式中的重要概念,确保一个类只有一个实例并提供全局访问点。本文详解单例模式的核心思想、实现方式及线程安全问题,包括基础实现(双重检查锁)、懒汉式与饿汉式对比,以及枚举实现的优势。通过代码示例和类图,深入探讨不同场景下的单例应用,如线程安全、防止反射攻击和序列化破坏等,展示枚举实现的简洁与可靠性。
111 0
|
7月前
|
设计模式 安全 Java
设计模式:单例模式
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。它通过私有化构造函数、自行创建实例和静态方法(如`getInstance()`)实现。适用于数据库连接池、日志管理器等需要全局唯一对象的场景。常见的实现方式包括饿汉式、懒汉式、双重检查锁、静态内部类和枚举。线程安全问题可通过`synchronized`或双重检查锁解决,同时需防止反射和序列化破坏单例。优点是避免资源浪费,缺点是可能增加代码耦合度和测试难度。实际开发中应优先选择枚举或静态内部类,避免滥用单例,并结合依赖注入框架优化使用。
|
8月前
|
设计模式 存储 安全
设计模式2:单例模式
单例模式是一种创建型模式,确保一个类只有一个实例,并提供全局访问点。分为懒汉式和饿汉式: - **懒汉式**:延迟加载,首次调用时创建实例,线程安全通过双重检查锁(double check locking)实现,使用`volatile`防止指令重排序。 - **饿汉式**:类加载时即创建实例,线程安全但可能浪费内存。 示例代码展示了如何使用Java实现这两种模式。
129 4
|
10月前
|
设计模式 存储 前端开发
前端必须掌握的设计模式——单例模式
单例模式是一种简单的创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。适用于窗口对象、登录弹窗等场景,优点包括易于维护、访问和低消耗,但也有安全隐患、可能形成巨石对象及扩展性差等缺点。文中展示了JavaScript和TypeScript的实现方法。
365 13
|
10月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
111 2
|
11月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
140 4
|
11月前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
11月前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
110 1