设计模式之单例模式

Posted TechNomad

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之单例模式相关的知识,希望对你有一定的参考价值。

一、单例模式简介

  在单例模式中,类的实例化只会发生一次,而后续的访问都会返回同一个实例。这样可以保证在整个应用程序中,只有一个实例存在,从而避免了多个实例对资源的重复使用或竞争的问题。单例模式通常被用于需要共享某些资源或状态的情况,例如数据库连接、日志记录器、配置管理器等。它可以提供一种简单而有效的方式来管理这些资源,同时确保在整个应用程序中只有一个实例。

单例模式的关键特点包括:

  1. 私有构造函数:单例类的构造函数被设置为私有,防止外部代码创建多个实例。
  2. 静态实例变量:单例类内部维护一个静态变量,用于保存类的唯一实例。
  3. 静态访问方法:通过一个静态的访问方法来获取单例实例,该方法负责创建实例(如果实例不存在)并返回该实例
  4. 拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性。

单例模式可以分为懒汉式 和饿汉式 ,懒汉式和饿汉式是单例模式中两种常见的实现方式,它们在实例化单例对象的时机上有所不同。

  1. 懒汉式(Lazy Initialization):

    • 懒汉式单例模式是在需要时才创建实例。也就是说,当第一次请求获取单例实例时才进行实例化。
    • 在懒汉式中,单例对象的实例化是延迟进行的,因此也被称为延迟加载。
    • 懒汉式实现相对简单,但在多线程环境下需要考虑线程安全性,需要进行同步控制,以避免多个线程同时创建多个实例。
  2. 饿汉式(Eager Initialization):

    • 饿汉式单例模式在程序启动时就创建实例。也就是说,单例对象的实例化发生在类加载阶段或者应用程序启动时。
    • 在饿汉式中,单例对象的实例在整个生命周期内都存在,并且可以被立即访问。
    • 饿汉式实现相对简单,不存在线程安全问题,但在某些情况下可能造成不必要的资源浪费,因为实例被提前创建而不管是否被使用。

区别总结如下:

  • 实例化时机:懒汉式是在需要时才进行实例化,而饿汉式是在程序启动时或类加载阶段就进行实例化。
  • 延迟加载:懒汉式是延迟加载实例,只有在需要时才创建,而饿汉式是提前创建实例,立即可用。
  • 线程安全性:懒汉式在多线程环境下需要考虑线程安全性,需要进行同步控制,而饿汉式不存在线程安全问题。
  • 资源消耗:懒汉式避免了不必要的资源消耗,只有在需要时才创建实例,而饿汉式可能造成不必要的资源浪费,因为实例被提前创建而不管是否被使用。

选择使用懒汉式还是饿汉式取决于具体的应用场景和需求。如果资源消耗较大或需要延迟加载,懒汉式是一个较好的选择。如果资源消耗较小且需要立即可用,饿汉式是一个简单有效的方案。另外,还可以考虑其他的单例实现方式,如双重检查锁定、静态内部类等,以满足特定的需求。

二、饿汉式单例模式在程序启动时就创建实例

#pragma once
class Singleton 

private:
    Singleton();
    Singleton(const Singleton &);
    Singleton& operator=(const Singleton &);
    ~Singleton();

public:
    static Singleton* getInstance();

private:
    static Singleton m_instance;
;
#include "Singleton.h"
#include <iostream>

Singleton Singleton::m_instance;

Singleton::Singleton()

    std::cout << "创建饿汉式单例对象" << std::endl;


Singleton::Singleton(const Singleton&)




Singleton& Singleton::operator=(const Singleton&)

    return m_instance;


Singleton::~Singleton()

    std::cout << "删除饿汉式单例对象" << std::endl;


Singleton* Singleton::getInstance()

    return &m_instance;
#include <iostream>
#include "Singleton.h"

int main() 
    system("pause");
    return 0;

打印结果:

 三、懒汉式单例模式是在需要时才创建实例

#pragma once
class Singleton 

private:
    Singleton();
    Singleton(const Singleton &);
    Singleton& operator=(const Singleton &);
    ~Singleton();

public:
    static Singleton* getInstance();

private:
    static Singleton *m_instance;
;
#include "Singleton.h"
#include <iostream>

Singleton *Singleton::m_instance = nullptr;

Singleton::Singleton()

    std::cout << "创建懒汉式单例对象" << std::endl;


Singleton::Singleton(const Singleton&)




Singleton& Singleton::operator=(const Singleton&)

    return *m_instance;


Singleton::~Singleton()

    std::cout << "删除懒汉式单例对象" << std::endl;


Singleton* Singleton::getInstance()

    if (m_instance == nullptr) 
        m_instance = new Singleton();
    

    return m_instance;
#include <iostream>
#include "Singleton.h"

int main() 
    Singleton *sin1 = Singleton::getInstance();
    Singleton* sin2 = Singleton::getInstance();
    Singleton* sin3 = Singleton::getInstance();
    return 0;

打印结果:

 可以看到,获取了三次类的实例,却只有一次类的构造函数被调用,表明只生成了唯一实例,这是个最基础版本的单例实现。但是同样上述的单例模式也有两个问题:

1.线程安全的问题:当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_instance是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_instance还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 
2.内存泄漏:注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。

因此,这里提供一个改进的,线程安全的、使用智能指针的实现;

#pragma once

#include <memory>
#include <mutex>

class Singleton 

private:
    Singleton();
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    ~Singleton();

public:
    static std::shared_ptr<Singleton> getInstance();

private:
    static std::shared_ptr<Singleton> m_instance;
    static std::mutex m_mutex;
;
#include "Singleton.h"
#include <iostream>

std::shared_ptr<Singleton> Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutex;

Singleton::Singleton()

    std::cout << "创建懒汉式单例对象" << std::endl;


Singleton::~Singleton()

    std::cout << "删除懒汉式单例对象" << std::endl;


std::shared_ptr<Singleton> Singleton::getInstance()

    if (m_instance == nullptr) 
        std::lock_guard<std::mutex> lock_mutex(m_mutex);
        if (m_instance == nullptr) 
            m_instance = std::shared_ptr<Singleton>(new Singleton());
        
    

    return m_instance;
#include <iostream>
#include "Singleton.h"

int main() 
    std::shared_ptr<Singleton> ptr1 = Singleton::getInstance();
    std::shared_ptr<Singleton> ptr2 = Singleton::getInstance();
    
    return 0;

打印结果如下,发现确实只构造了一次实例,并且发生了析构

 shared_ptr和mutex都是C++11的标准,以上这种方法的优点是

  • 基于 shared_ptr, 用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏。
  • 加了锁,使用互斥量来达到线程安全。这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 getInstance的方法都加锁,锁的开销毕竟还是有点大的。

四、局部静态变量实现懒汉式单例

#pragma once

class Singleton 

private:
    Singleton();
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    ~Singleton();

public:
    static Singleton& getInstance();

;
#include "Singleton.h"
#include <iostream>

Singleton::Singleton()

    std::cout << "创建懒汉式单例对象" << std::endl;


Singleton::~Singleton()

    std::cout << "删除懒汉式单例对象" << std::endl;


Singleton& Singleton::getInstance()

    static Singleton instance;

    return instance;
#include <iostream>
#include "Singleton.h"

int main() 
    Singleton& sin1 = Singleton::getInstance();
    Singleton& sin2 = Singleton::getInstance();
    
    return 0;

打印结果:

这是最推荐的一种单例实现方式:

  1. 通过局部静态变量的特性保证了线程安全;
  2. 不需要使用共享指针,代码简洁;
  3. 注意在使用的时候需要声明单例的引用 Single& 才能获取对象。

 

JAVA设计模式之单例模式

JAVA设计模式之单例模式
概念:
  java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。
  单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例.

  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
一、懒汉式单例

//懒汉式单例类.在第一次调用的时候实例化自己 
public class Singleton {
    private Singleton() {}
    private static Singleton single=null;
    //静态工厂方法 
    public static Singleton getInstance() {
         if (single == null) {  
             single = new Singleton();
         }  
        return single;
    }
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:

1、在getInstance方法上加同步

public static synchronized Singleton getInstance() {
         if (single == null) {  
             single = new Singleton();
         }  
        return single;
}

2、双重检查锁定

public static Singleton getInstance() {
        if (singleton == null) {  
            synchronized (Singleton.class) {  
               if (singleton == null) {  
                  singleton = new Singleton(); 
               }  
            }  
        }  
        return singleton; 
    }

3、静态内部类

public class Singleton {  
    private static class LazyHolder {  
       private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
       return LazyHolder.INSTANCE;  
    }  
}  

这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。
二、饿汉式单例

//饿汉式单例类.在类初始化时,已经自行实例化 
public class Singleton1 {
    private Singleton1() {}
    private static final Singleton1 single = new Singleton1();
    //静态工厂方法 
    public static Singleton1 getInstance() {
        return single;
    }
}

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的
参考文献:
https://blog.csdn.net/jason0539/article/details/2329703

以上是关于设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之单例模式

JAVA设计模式之单例模式(转)

JAVA设计模式之单例模式

设计模式之单例模式

单例模式之单例模式

设计模式之单例模式