单例模式(Singleton Design Pattern)理解起来非常简单,一个类只允许创建一个实例,这个类就属于一个单例类。
适用范围
- 处理共享资源的访问冲突。
- 需要设置全局唯一类,比如日志类、数据库连接类、全局配置文件类等。
- 需要频繁实例化然后销毁的对象。
单例类的分类
需要注意的地方:
单例模式在 多线程 必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例, 这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
饿汉式
饿汉式:当一个类在引入或加载时,实例就创建并初始化。
直接在模块中生成类的实例对象:
class Log:
pass
# 其他模块通过 import log 使用实例
log = Log()
优点
- 提前创建好实例,避免在程序运行的时候,再去初始化导致的性能问题。
- 按照 fail-fast 的设计原则(有问题及早暴露),有问题时初始化就能暴露(比如内存溢出),不用等到线上运行时,影响系统可用性。
- 线程安全的
缺点
- 初始化资源耗时,如果需要初始化的对象很多,程序加载时间太久,体验不好。
- 资源效率不高,实例初始化出来后可能一直没有使用到,造成浪费
懒汉式
懒汉式:延迟加载,只有在实例需要使用时,才初始化实例。
懒汉式在python有几种使用方法:
- 函数装饰器
def borg(cls):
"""
通过函数装饰器来实现一个类
"""
_instance = dict()
def singleton(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return singleton
@borg
class Log1:
def __init__(self):
print("aaa")
- 类装饰器
class Borg:
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls
return self._instance[self._cls]
@Borg
class Log2:
def __init__(self):
print("aaa")
- 通过 python 的 new 方法
class Log:
"""
通过 __new__ 方法实现一个单例类
通过一个静态变量 _instance 用来保证实例唯一性
"""
_instance = None
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
return cls._instance
优缺点与上面饿汉式正好相反
双重检测
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。有一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。
通过双重检测方式,我们要保证同一时刻,多个线程只有一个能够操作实例的创建,我们可以通过多线程锁来实现。只要实例被创建之后,即便再调用 get_instance 函数也不会再进入到加锁逻辑中了
具体实例如下:
from threading import Lock
class Log4:
"""
通过一个多线程锁保证同一时刻最多只有一个线程能创建实例
不能通过 new 方法创建实例
"""
_lock = Lock()
def __new__(cls, *args, **kwargs):
msg = "Cann't new a SingletonSample class, please use get_instance method!"
raise NotImplementedError(msg)
@classmethod
def get_instance(cls):
if not hasattr(cls, "_instance"):
with cls._lock:
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
return cls._instance
总结
这篇文章主要总结了一下自己学习 单例模式 时学到的内容,一般 单例模式 都是采用饿汉式,比较少采用懒汉式及双重检测。