设计模式之单例模式(Singleton Pattern)
一、概述
单例模式是最简单的设计模式之一。此设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式包含一个单一的类,该类负责创建自己的对象,同时确保该类仅有一个实例。并且提供了一个访问其对象的全局访问点,可以直接访问,不需要实例化该类的对象。
总结:
- 单例模式中的类只能有一个实例。
- 该实例只能由自己创建,不可通过外部创建。
- 该类必须给外部对象一个访问点
二、应用实例
1、一个党只能有一个主席。
2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
4、一个系统只能有一个窗口管理器或文件系统
5.、一个系统只能有一个计时工具或ID(序号)生成器
三、实现方式
1.构造函数私有化
①类外不可创建对象
②构造函数私有化的函数不能被继承
2.提供静态方法创建成员
构造函数私有化-->类外不可创建对象-->初始时类外不能调用类内非静态公有函数-->在类内创建静态函数用来创建对象
四、类图
instance | 私有静态指针变量,用于存储唯一对象的指针 |
Singleton() | 私有构造方法,用于生成对象 |
getInstance() | 公用方法,提供给用户一个接口来获得对象指针 |
五、实现分析
以上我们了解了单例模式的特点以及实现单例模式的思路。
然而单例模式中由于对象的初始化的时间不同产生了两种情况(懒汉与饿汉):
①懒汉:顾名思义,它很懒,如果客户不需要实例,它就不会初始化对象,只有在客户需要时调用getInstance方法生成对象。
特点:用时间换空间,对象延时生成,只在需要时才会生成的方法节省了空间
饿汉:人在饿的时候都会饥不择食,所有此种情况在类加载时就生成对象
特点:用空间换时间,在类初始化时就生成对象,占用了内存,但在调用时不需要重新生成,节省了时间。
虽然以上我们分析的比较详细了,但是在计算机中,计算机运行结果不一定如我们所料,因为计算机内部充满着并发。所以在对单例模式进行初始化操作时,如果两个线程同时调用了getInstance方法,岂不是会形成两个对象,这就与我们的初衷相违背。所以之后我们介绍线程不安全和线程安全的两种实现形式。
六、代码及说明
1.C++
①懒汉、线程不安全
Code:
#include<iostream> using namespace std; class Singleton { private: static Singleton *instance; Singleton() { ///////; } public: static Singleton *getInstance() { if(!instance) instance=new Singleton(); return instance; } }; Singleton *Singleton::instance=NULL; int main() { Singleton *p=Singleton::getInstance(); Singleton *q=Singleton::getInstance(); cout<<p<<endl; cout<<q<<endl; }
Result:
②懒汉,线程安全(加锁)
Code:
class Singleton { private: static Singleton *instance; Singleton() { pthread_mutex_init(&mutex,NULL); } public: static pthread_mutex_t mutex; static Singleton *getInstance() { pthread_mutex_lock(&mutex); if(!instance) instance=new Singleto(); pthread_mutex_unlock(&mutex); return instance; } }; pthread_mutex_t Singleton::mutex; Singleton *Singleton::instance=NULL;
③懒汉,线程安全(内部静态变量)(推荐)
Code:
class Singleton{ private: Singleton() { ///////; } public: static Singleton *getInstance() { static Singleton instance; return &instance; } };
④饿汉,线程安全(推荐)
Code:class Singleton { private: static Singleton *instance; Singleton() { ///////; } public: static Singleton *getInstance() { return instance; } }; Singleton *Singleton::instance=new Singleton();
2.JAVA
①懒汉,线程不安全
Code:
public class Singleton { private static Singleton instance; private Singleton() { ///////; } public static Singleton getInstance() { if(instance==null) { instance=new Singleton(); } return instance; } public static void main(String args[]) { Singleton p=Singleton.getInstance(); Singleton q=Singleton.getInstance(); System.out.println(p); System.out.println(q); } }
Result:
②懒汉,线程安全(加锁1)
但每次调用方法都会产生同步,所以性能不好
public class Singleton { private static Singleton instance; private Singleton() { ///////; } public static synchronized Singleton getInstance() { if(instance==null) { instance=new Singleton(); } return instance; } }
③懒汉,线程安全(加锁2,②的改进版)
只有第一次调用方法时,才进行同步,但是可能多线程同时第一次调用,都通过第一次判空,所以在同步时,应该再次进行判空。
public class Singleton { private static Singleton instance; private Singleton() { ///////; } public static Singleton getInstance() { if(instance==null) { synchronized(Singleton.class) { if(instance==null) instance=new Singleton(); } } return instance; } }
④懒汉,线程安全(静态内部类)(推荐)
该方法利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗
public class Singleton { private Singleton() { ///////; } private static class LazyHolder { private static final Singleton instance=new Singleton(); } public static Singleton getInstance() { return LazyHolder.instance; } }
⑤饿汉,线程安全(推荐)
public class Singleton { private static Singleton instance=new Singleton(); private Singleton() { ///////; } public static Singleton getInstance() { return instance; } }
六、优缺点
1.优点
①实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
②灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
2.缺点
①开销
饿汉方式每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。
②可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
③对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中,只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。
本章为所有设计模式的第一章,在进行编写的时候查阅了很多资料并且学到了很多,希望和大家一起分享。
如果在编写的时候出现了错误(尤其是代码部分)还希望大家积极指正。也希望有独特见解的朋友能够不吝赐教,与君共勉。