Post

设计模式系列-单例模式

顾名思义,单例模式(Singleton Pattern)就是保证一个类有且仅有一个实例,并且提供了一个全局的访问点。这就要求我们绕过常规的构造器,提供一种机制来保证一个类只有一个实例,客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任。

举例:一个应用程序的日志,应用程序的多处都要调用日志进行记录,因此保证类只有一个实例可以将日志内容记录到统一的文件内,防止多个日志实例对象同时写入该文件导致乱序、访问错误等问题。

实现方法-JAVA

1.懒汉式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Logger {
    private static Logger uniqueInstance;
    private Logger(String output_file) {
        // 初始化日志系统,包括日志文件路径、格式设置等
        // ......
    }

    public static Logger getUniqueInstance(String output_file) {
        if (uniqueInstance == null) {
            uniqueInstance = new Logger(output_file);
        }
    return  uniqueInstance;
    }
}

说明: 先不创建实例,当第一次被调用时,再创建实例,所以被称为懒汉式。 优点: 延迟了实例化,如果不需要使用该类,就不会被实例化,节约了系统资源。 缺点: 线程不安全,多线程环境下,如果多个线程同时进入了 if (uniqueInstance == null) ,若此时还未实例化,也就是uniqueInstance == null,那么就会有多个线程执行 uniqueInstance = new Singleton(); ,就会实例化多个实例;

2.饿汉式(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Logger {
    private static output_file = "xxx";
    private static Logger uniqueInstance = new Logger(output_file);
    private Logger(String output_file) {
        // 初始化日志系统,包括日志文件路径、格式设置等
        // ......        
    }

    public static Logger getUniqueInstance() {
        return uniqueInstance;
    }

}

说明: 先不管需不需要使用这个实例,直接先实例化好实例 (饿死鬼一样,所以称为饿汉式),然后当需要使用的时候,直接调方法就可以使用了。 优点: 提前实例化好了一个实例,避免了线程不安全问题的出现。 缺点: 直接实例化好了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,都会操作系统的资源浪费。

3.懒汉式(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Logger {
    private static Logger uniqueInstance;
    private Logger(String output_file) {
        // 初始化日志系统,包括日志文件路径、格式设置等
        // ......
    }

    public static synchronized Logger getUniqueInstance(String output_file) {
        if (uniqueInstance == null) {
            uniqueInstance = new Logger(output_file);
        }
        return uniqueInstance;
    }
}

说明: 实现和线程不安全的懒汉式几乎一样,唯一不同的点是,在get方法上加了一把锁。如此一来,多个线程访问,每次只有拿到锁的的线程能够进入该方法,避免了多线程不安全问题的出现。 优点: 延迟实例化,节约了资源,并且是线程安全的。 缺点: 虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了,既后续不会再出现线程安全问题了,但是锁还在,每次还是只能拿到锁的线程进入该方法,会使线程阻塞,等待时间过长。

其余方法

双重检查锁实现(线程安全) 静态内部类实现(线程安全) 枚举类实现(线程安全) 本人主要语言不是java因此不深入介绍了

实现方法-Python

1.函数装饰器

使用_instance字典,不可变的类地址作为键、其实例作为值,记录已经创建的类实例。如果一个类已经创建了实例,则返回该实例,实现了单例模式。 实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def singleton(loggger):
    _instance = {}
    def inner():
        if cls not in _instance:
            _instance[cls] = cls()
        return _instance[cls]
    return inner
    
@singleton
class Logger(object):
    def __init__(self):
        pass

log1 = Logger()
log2 = Logger()
print(id(log1) == id(log2))
# 输出结果:
# True

内置函数id,返回对象的“标识值”。该值是一个整数,在此对象的生命周期中保证是唯一且恒定的。输出结果为True表明,log1和log2指向了同一个对象。

2.类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton(object):
    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]
@Singleton
class Logger2(object):
    def __init__(self):
        pass

log1 = Logger2()
log2 = Logger2()
print(id(log1) == id(log2))

和函数装饰器实现同理

3.new关键字实现单例模式

使用 new 方法在创造实例时进行干预,达到实现单例模式的目的:

1
2
3
4
5
6
7
8
9
10
11
12
class Single(object):
    _instance = None
    def __new__(cls, *args, **kw):
        if cls._instance is None:
            cls._instance = object.__new__(cls, *args, **kw)
        return cls._instance
    def __init__(self):
        pass

single1 = Single()
single2 = Single()
print(id(single1) == id(single2))

此处使用_instance来存放一个实例

4.metaclass元类

#todo

参考: java单例模式的六种实现 https://juejin.cn/post/6844904121837830151 Python单例模式(Singleton)的N种实现 https://zhuanlan.zhihu.com/p/37534850

This post is licensed under CC BY 4.0 by the author.