设计模式之单例模式
Posted TechNomad
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之单例模式相关的知识,希望对你有一定的参考价值。
一、单例模式简介
在单例模式中,类的实例化只会发生一次,而后续的访问都会返回同一个实例。这样可以保证在整个应用程序中,只有一个实例存在,从而避免了多个实例对资源的重复使用或竞争的问题。单例模式通常被用于需要共享某些资源或状态的情况,例如数据库连接、日志记录器、配置管理器等。它可以提供一种简单而有效的方式来管理这些资源,同时确保在整个应用程序中只有一个实例。
单例模式的关键特点包括:
- 私有构造函数:单例类的构造函数被设置为私有,防止外部代码创建多个实例。
- 静态实例变量:单例类内部维护一个静态变量,用于保存类的唯一实例。
- 静态访问方法:通过一个静态的访问方法来获取单例实例,该方法负责创建实例(如果实例不存在)并返回该实例
- 拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性。
单例模式可以分为懒汉式 和饿汉式 ,懒汉式和饿汉式是单例模式中两种常见的实现方式,它们在实例化单例对象的时机上有所不同。
-
懒汉式(Lazy Initialization):
- 懒汉式单例模式是在需要时才创建实例。也就是说,当第一次请求获取单例实例时才进行实例化。
- 在懒汉式中,单例对象的实例化是延迟进行的,因此也被称为延迟加载。
- 懒汉式实现相对简单,但在多线程环境下需要考虑线程安全性,需要进行同步控制,以避免多个线程同时创建多个实例。
-
饿汉式(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;
打印结果:
这是最推荐的一种单例实现方式:
- 通过局部静态变量的特性保证了线程安全;
- 不需要使用共享指针,代码简洁;
- 注意在使用的时候需要声明单例的引用 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
以上是关于设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章