单例模式
定义
单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
全局只能够创建一个类的实例,因此需要一种只生成一个实例的机制。通常会给类定义一个静态方法来实现。
实现方式
几个关键点
- 单例模式的类只能够创建一个全局的实例
- 类需要自己创建实例,因此构造函数要是私有的,不能够对外提供
- 类内部持有该类唯一的实例
- 类对外提供获取唯一实例的接口
懒汉式
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
class LazySingleton:
_flag = False
_lock = threading.RLock()
def __init__(self):
if not LazySingleton._flag:
# init
LazySingleton._flag = True
@classmethod
def instance(cls, *args, **kwargs) # 第一种方式
if not hasattr(LazySingleton, "_instance"):
LazySingleton._instance = LazySingleton(args, kwargs)
return LazySingleton._instance
def __new__(cls, *args, **kwargs):
with cls._lock:
if not hasattr(cls, "_instance"):
cls._instance = super(LazySingleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
饿汉式
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
某些情况下,造成内存浪费,因为对象未被使用的情况下就会被初始化,如果一个项目中的类多达上千个,在项目启动的时候便开始初始化可能并不是我们想要的。
class HungrySingleton() {
private:
HungrySingleton(){}
HungrySingleton(HungrySingleton s&) = delete;
HungrySingleton& operator=(const HungrySingleton& h) = delete;
static HungrySingleton _instance = HungrySingleton();
public:
static HungrySingleton& get_instance() {
return _instance;
}
}
HungrySingleton HungrySingleton::_instance = HungrySingleton();
python特有的实现
模块实现
python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:
class Singleton:
def __init__():
pass
singleton = Singleton
然后在另一个文件中import使用即可
from Singleton import singleton
类装饰器实现
class SingleTon:
_instance_dict = {}
def __init__(self, cls_name):
self.cls_name = cls_name
def __call__(self, *args, **kwargs):
if self.cls_name not in SingleTon._instance_dict:
SingleTon._instance_dict[self.cls_name] = self.cls_name(*args, **kwargs)
return SingleTon._instance_dict.get(self.cls_name)
@SingleTon # 这个语法糖相当于Student = SingleTon(Student),即Student是SingleTon的实例对象
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
# 原理:在函数装饰器的思路上,将装饰器封装成类。
# 程序执行到与语法糖时,会实例化一个Student对象,这个对象是SingleTon的对象。
# 后面使用的Student本质上使用的是SingleTon的对象。
# 所以使用Student('jack', 18)来实例化对象,其实是在调用SingleTon的对象,会触发其__call__的执行
# 所以就在__call__中,判断Student类有没有实例对象了。
函数装饰器实现
def singleton(cls):
_instance_dict = {} # 采用字典,可以装饰多个类,控制多个类实现单例模式
def inner(*args, **kwargs):
if cls not in _instance_dict:
_instance_dict[cls] = cls(*args, **kwargs)
return _instance_dict.get(cls)
return inner
@singleton
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
优缺点
优点
- 实例全局唯一,可以解决一些资源冲突的问题
- 实例化过程属于类内部的实现,不对外呈现,可以灵活变动实例化过程
缺点
- 每次获取实例都需要判断是否已初始化,可通过静态初始化来解决
- 有些语言可以允许其他类删除该实例,会导致野指针引用
应用场景
在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。