单例设计模式

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。

  • 为什么需要用到单例模式

在我们一个系统中,什么时候需要用到单例设计模式呢?

例如在一个系统中,我们需要去连接数据库,大家都知道连接数据库是一件非常耗时的事情!

现在我们可以考虑使用连接池的方式来解决这个事情:

  1. 创建一个类,叫做dbpool
  2. 在这个类的init函数中我们创建10个数据库连接,并将它们保存到队列中
  3. 现在隔壁老王想用这个池子,那它就 dbpool(),创建了这个类的实例, 它里面初始化了10个连接
  4. 一会隔壁老张也想用个池子,那它就dbpool(),创建了这个类的实例, 它里面又重新初始化了10个连接

大家会发现,在我们这个故事中,设计池子这件事很合理,只不过呢使用的时候,每次都重新初始化了池中的10个连接,那怎么让我们这个数据库dbpool只初始化一次呢 ?

接下来,我们首先创建一个单例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Singleton1():
    _instance = None
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls,*args,**kwargs)

    def __init__(self):
        # 假装做了耗时操作
        time.sleep(3)

    @classmethod
    def get_instance(cls):

        if cls._instance is None:
            cls._instance = object.__new__(cls);

        # 返回类的实例
        return cls._instance;

这个类,我们直接调用是没有任何问题的,但是如果我们使用多线程去访问它,那就有问题啦!

我们可以来测试一下这个类, 你会发现它还是出现了创建了多个实例

1
2
3
4
5
6
7
import unittest

class TestSingleton(unittest.TestCase):

    def testSingleton1(self):
        for i in range(20):
            threading.Thread(target=lambda :print(Singleton1())).start()

问题其实就出在了__init__中做了耗时的操作,接下来,我们给大家一种解决方案!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Singleton():

    _instance_lock = threading.Lock();

    def __init__(self):
        print("init")
        time.sleep(5)

    def __new__(cls, *args, **kwargs):

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

        return cls._instance;

我们再使用前面的套路来测试一下它:

1
2
3
    def testSingleton2(self):
        for i in range(20):
            threading.Thread(target=lambda :print(Singleton())).start()

至此,我们这个Singleton类就完成啦! 无论调用多少次,其实最终都只返回一个实例!