设计模式笔记 – 单例模式

单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

介绍

  • 意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决: 一个全局使用的类频繁地创建与销毁。
  • 何时使用: 当您想控制实例数目,节省系统资源的时候。
  • 如何解决: 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码: 构造函数是私有的。
  • 优点:
    1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
    2. 避免对资源的多重占用(比如写文件操作)。
  • 缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
  • 应用实例:
    1. 一个班级只有一个班主任。
    2. Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
    3. 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
  • 使用场景:
    1. 要求生产唯一序列号。
    2. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
    3. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
  • 注意事项: getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
    1. 单例类只能有一个实例
    2. 单例类必须自己创建自己的唯一实例
    3. 单例类必须给所有其他对象提供这一实例

示例

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。

Java 实现

饿汉式

是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

双重校验锁版

JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

静态内部类

是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

Python 实现

对于python来说,单例注意点的第三点存在争议。暂时忽略
以下两种Python的单例实现都是Lazy的。
通过自己添加obj = Singleton()即可创建单例实例(即饿汉式),然后被其他模块import得到。

metaclass法

是否多线程安全:是
实现难度:相对复杂
优点:用于创建Singleton的metaclass可以复用,多次调用时不会重复调用__init__方法

import threading

class SingletonMetaclass(type):
    __instance_lock = threading.Lock()
    __instance = None

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

class Singleton(metaclass=SingletonMetaclass):
    pass

class Singleton2(metaclass=SingletonMetaclass):
    pass

obj = Singleton()
obj2 = Singleton2()

__new__

是否多线程安全:是
实现难度:易
缺点:多次调用时会重复调用__init__方法

import threading

class Singleton(object):
    __instance_lock = threading.Lock()
    __instance = None

    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            with cls.__instance_lock:
                if cls.__instance is None:
                    cls.__instance = object.__new__(cls, *args, **kwargs)
        return cls.__instance

obj = Singleton()

关于重复调用__init__方法的研究

  • __new__是在一个对象实例化的时候所调用的第一个方法
  • __new__决定是否要使用__init__方法,如果__new__没有返回实例对象,则__init__不会被调用

因此,如果使用__new__方法来创建一个单例,无可避免地会导致重复调用__init__。可以添加一个__inited属性,在__init__方法中判断是否为True

You may also like...

发表评论

您的电子邮箱地址不会被公开。