单例模式
单例模式的主要目的是确保一个类在当前系统中有且仅有一个实例能够被全局访问。
特点
- 唯一实例:一个类只有一个实例,且该实例一直存在。
- 全局访问:通过静态方法提供全局唯一的访问入口,避免创建多个实例。
简单示例
1.懒汉式
## 非线程安全的单例实现
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
## 线程安全的单例实现
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
2.饿汉式
饿汉式的代码实现与懒汉式一样,只是在类定义的时候就完成了初始化,而不是在调用的时候初始化。
维度 | 懒汉式 | 饿汉式 |
---|---|---|
初始化时机 | 首次创建对象时 | 模块被加载时/类定义时 |
线程安全 | 需要自己加锁 | 天生就是 |
资源占用 | 按需占用(使用到了才要) | 一开始就占用(即使一直没被调用) |
因为python
语言特性,模块模块加载时,就会执行模块的代码,所以,在模块中定义一个类,并创建一个实例,这个实例就是饿汉式单例。
# config.py
class Config:
def __init__(self):
pass
config = Config() # 直接在定义类的时候进行初始化
# 在别的模块使用时:
from config import config # 这就利用模块导入机制实现的天生线程安全的饿汉式单例
3.装饰器实现的单例模式
def singleton(cls):
instance = {}
lock = threading.Lock()
def wrapper(*args, **kwargs):
if cls not in instance:
with lock:
if cls not in instance:
instance[cls] = cls(*args, **kwargs)
return instance[cls]
return wrapper
@singleton
class Logger:
def __init__(self):
print('init')
log1 = Logger()
log2 = Logger()
log1 is log2 # True
应用场景
- 配置管理:在应用程序中,通常需要读取配置文件,比如数据库连接信息、应用程序的日志级别等。如果每次都读取配置文件,可能会导致性能问题,因此,可以使用单例模式来管理配置文件,保证全局唯一。
- 数据库连接:在应用程序中,通常需要连接到数据库,比如MySQL、PostgreSQL等数据库。如果每次都创建数据库连接,可能会导致性能问题,因此,可以使用单例模式来管理数据库连接,保证全局唯一。
- 日志管理:在应用程序中,通常需要记录日志,比如错误日志、访问日志等。如果每次都创建日志对象,可能会导致性能问题,因此,可以使用单例模式来管理日志对象,保证全局唯一。
- 缓存管理:在应用程序中,通常需要使用缓存来存储数据,比如Redis、Memcached等缓存系统。如果每次都创建缓存对象,可能会导致性能问题,因此,可以使用单例模式来管理缓存对象,保证全局唯一。
优缺点
- 全局唯一,全局都可访问,无需传递参数。
- 资源优化,避免重复创建对象。
- 线程安全,避免多线程并发问题。
- 耦合度高,因为是全局状态导致的多处引用,修改起来比较麻烦。
- 资源浪费,如果单例对象比较大,那么在创建的时候就会占用一定的内存,特别是饿汉式。
- 测试困难,单例的生命周期贯穿整个应用,很难进行单元测试。