单例模式

为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。

适用场景:

  • 全局共享一个配置文件,例如都通过AppConfig类读取
  • 全局管理类(Manager)
  • 日志记录工具,一般也是单例,方便同步、追加
  • 全局的计数器
  • 数据库连接池、多线程的线程池
  • ……

# Java 单例模式

推荐看看这个视频教程 (opens new window)

核心是造器私有,别人就不能在外部创建对象了,然后提供一个公有静态方法让外部获取实例。

# 饿汉式

程序一开始就创建了这个对象,所以叫饿汉式。

/**
 * 单例模式-饿汉式
 */
public class Hungry {
    private Hungry() {

    }
    private final static Hungry INSTANCE = new Hungry();

    public static Hungry getInstance() {
        return INSTANCE;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 懒汉式

饿汉式可能会浪费内存空间,于是出现了懒汉式,在第一次需要用到的时候再创建对象。

/**
 * 单例模式-懒汉式
 */
public class Lazy {
    private Lazy() {

    }
    // 必须要加volatile,使new的时候是个原子操作,避免在new还没结束的时候,别的线程访问了该地址返回了一个虚空的对象(发生未知异常)
    private volatile static Lazy INSTANCE;

    public Lazy getInstance() {
        if (INSTANCE == null) {
            // 双重检测锁模式的懒汉模式(DCL):为了确保多线程安全,这里加锁
            synchronized (Lazy.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Lazy();
                }
            }
        }
        return INSTANCE;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 静态内部类实现单例

/**
 * 静态内部类实现单例模式
 */
public class InnerStatic {
    private InnerStatic()  {

    }

    public static InnerStatic getInstance() {
        return InnerClass.INSTANCE;
    }

    public static class InnerClass{
        private static final InnerStatic INSTANCE = new InnerStatic();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 枚举实现单例模式

这是实现单例模式的最佳方法,多线程安全。前面几种方式其实都能被反射破坏安全性,而使用枚举的方式不会被反射破坏。

/**
 * 枚举实现单例模式,最安全,反射也不能破坏!
 */
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance() {
        return INSTANCE;
    }
}
1
2
3
4
5
6
7
8
9
10

为什么反射不能创建枚举的实例呢?在 java.lang.reflect.Constructor#newInstance 中有这么一段:

image-20220506100432365

可以看到如果是枚举类型直接抛出异常了,JDK反射机制内部完全禁止了用反射创建枚举实例的可能性。

# Python 单例模式

参考:Python中的单例模式的几种实现方式的及优化 (opens new window)

# 重写__new()__方法

在Python中当我们实例化一个对象时,是先执行了类的__new__方法(我们没写时,默认调用object.__new__)实例化对象。然后再执行类的__init__方法,对这个对象进行初始化。所以我们可以基于这个,实现单例模式,并且支持多线程:

import threading


class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = object.__new__(cls)
        return Singleton._instance


if __name__ == '__main__':
    # test
    obj1 = Singleton()
    obj2 = Singleton()
    print(id(obj1), id(obj2))

    # test multithreading
    def task(args):
        obj = Singleton()
        print(id(obj))


    for i in range(10):
        t = threading.Thread(target=task, args=[i, ])
        t.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

注意和 Java 中的单例不同,Java 构造器私有禁止在外部new对象,而python无法做到构造器私有,创建单例仍然使用普通的声明对象格式singleton = Singleton(),只是在实例化时返回的是同一个对象。

# 使用模块实现天然单例

由于模块的import机制,在程序运行期间,同一个模块只会加载一次,所以从模块中导入的对象只有一个,无论import多少次,得到的都是同一个对象。Python的模块不仅是单例模式,还是线程安全的单例模式,即使多线程并发导入同一个模块,也不会重复装载模块。

mysingleton.py

class Singleton(object):
    def foo(self):
        pass


singleton = Singleton()
1
2
3
4
5
6

使用:

from mysingleton import singleton


if __name__ == '__main__':
    print(id(singleton))
1
2
3
4
5

# 使用装饰器

def singletonDecorator(cls, *args, **kwargs):
    """
    定义一个单例装饰器
    """
    instance = {}

    def wrapperSingleton(*args, **kwargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]

    return wrapperSingleton


@singletonDecorator
class Singleton:
    """
    使用单例装饰器修饰一个类
    """

    def __init__(self):
        pass


if __name__ == '__main__':
    a = Singleton()
    b = Singleton()
    print(id(a))
    print(id(b))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 基于metaclass方式实现

参考:使用元类控制实例的创建 (opens new window)

# 所有使用Singleton作为元类的类都将是单例模式
class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__call__(*args, **kwargs)
            return cls.__instance
        else:
            return cls.__instance


class CustomClass(metaclass=Singleton):
    def __init__(self):
        print('Creating Spam')


if __name__ == '__main__':
    a = CustomClass()
    b = CustomClass()
    print(id(a))
    print(id(b))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24