包你懂设计模式之:单例模式(三种实现)

单例模式属于创建型的设计模式,它的产生是为了解决“一个类只能有一个实例”,它的定义为:“保证一个类只能有一个实例,并提供一个访问它的全局访问点”,从字面上的理解是很简单的,且看如何操作实现。

一、常规实现

1、私有化构造函数,不允许外部实例化

创建对象的实例我们用 New 关键字,后面跟上类的构造方法,这样我们就完成了一次对象的创建,很显然我们按照这样的方法操作多次就会产生多个实例,所以我们为了防止类被多次实例化,首先要做的就是将构造函数私有化(public 改成 private)。

2、提供一个外部获取实例的静态方法,实例只能找我要

构造函数被私有化后,外部不能创建对象的实例,必须要提供一个获取实例的方法。这样做的好处在于,实例的创建永远在类的内部实现,这样我们就可以通过一些逻辑来实现单一实例。

3、申明一个静态私有变量,用来存放类的实例

我们申明这个变量用来存放类的实例,在对外提供的获取实例的方法中,我们可以判断这个变量,如果这个变量为空,那么我们实例化后将实例保存在变量中,然后将变量return,反之如果变量不为空,那么直接返回变量即可。这样我们只有在第一次索要实例的时候会创建,后面就会一直使用这个实例,不会重新创建。

 以上我们通过简单的三部,实现了单例模式,类的外部通过GetInstence()方法可以获取到类的实例,在GetInstence()方法的内部我们限制了实例的创建,保证只产生一个实例。

以上其实只能保证在单线程时只产生一个单例,多线程则不能,我们先看一下在多线程下运行的效果:

 以上右侧部分代码,我模拟了10个线程同时获取实例的情况,在构造函数中我会输出一句话,表示实例被创建,来看一下运行的效果

可以看到实例不止创建了一次,但也不是创建了十次,我们分析代码可以发现这样一个问题:在语句中可能出现问题,当先执行到几个线程同时到达该判断的时候,由于这个实例还没创建,所以会一起通过判断,然后执行里面的实例化代码,导致我们的单例实现破功!

为了解决多线程下单例失效的情况,我们常见的作法就是对此加锁,以下是我修改后的代码:

 这样在多线程下我们也能确保实例只能被创建了一次,结果就不展示了,是可以的。

 以上的代码在单线程和多线程情况下虽然都能保证单一实例,但是仍然不是最优解,还可进一步的改造。我们分析一下代码,就会发现,本是多线程的,跑到这边必须要单个通过,就好比十车道突然边单车道,引起了交通堵塞,我们要想法改善一下。我们注意看锁住部分的代码,它里面的逻辑是用于实例的创建,但是如果这个实例已经被创建,也就是说变量_china已经不是null,说明实例已经被创建过一次,所以再进去锁里面的代码跑一趟,毫无意义,所以我又对代码进行了重构:

以上的代码,是最终完成版的单例模式实现。但请留意我上面标注的红框部分代码,两个同样的if判断,有人说内部的if可以删掉,你觉得可以么?留言见。

二、其他操作(静态初始化)

1、借力静态构造函数

下面看我的骚操作,我把代码修改成这样:

这种实现和第一种实现有本质上的不同,这边用到了静态构造函数的概念(静态构造函数用于初始化任何 静态 数据,或用于执行仅需执行一次的特定操作。 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数。),利用静态函数只被调用一次的特性,我们也可以实现单例,并且多线程也适用。由于构造函数被私有化,并且静态构造函数是没办法调用的,所以外部也是只能通过GetInstence来获取实例。

2、别忘了静态字段

 静态字段和静态构造函数有同样的特性,他也是在第一次使用这个类之前自动调用,并且只调用一次。根据这个我们还可以这样实现单例:

三、总结 

 虽然以上三种方法都能实现单例模式,但是在某些方面可能存在区别,需要我们继续是探讨,然后采用最合适的方法来实现。

第一种方法,在自己第一次被引用时会出发实例化,而后面的两种在自己被加载的时候会主动实例化,所以形象的将第一种称为“懒汉式单例类”,另外两种为“饿汉式单例类”。

学习了单例模式我们就应该思考它的引用场景,学会设计模式怎么用其实不难,难在你学会在什么样的场景下选择恰当的模式解决问题!单例模式适用于只能有一个实例的情况,比如类似于数据库连接池、线程池只能有一个,里面可以包含若干个链接,但应该只有一个池,还有取流水号、读取配置文件的时候,都可能用到单例模式。

以上如有纰漏或不准确的地方,还请你留言指正,大家共同学习进步!

猜你喜欢

转载自blog.csdn.net/maaici/article/details/107972871