设计模式之单例模式(Singleton Pattern)
一、概述
单例模式是最简单的设计模式之一。此设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式包含一个单一的类,该类负责创建自己的对象,同时确保该类仅有一个实例。并且提供了一个访问其对象的全局访问点,可以直接访问,不需要实例化该类的对象。
总结:
- 单例模式中的类只能有一个实例。
- 该实例只能由自己创建,不可通过外部创建。
- 该类必须给外部对象一个访问点
二、应用实例
1、一个党只能有一个主席。
2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
4、一个系统只能有一个窗口管理器或文件系统
5.、一个系统只能有一个计时工具或ID(序号)生成器
三、实现方式
1.构造函数私有化
①类外不可创建对象
②构造函数私有化的函数不能被继承
2.提供静态方法创建成员
构造函数私有化-->类外不可创建对象-->初始时类外不能调用类内非静态公有函数-->在类内创建静态函数用来创建对象
四、类图
![](https://i-blog.csdnimg.cn/blog_migrate/e7dfaa1875ac6796cf5036d419674653.png)
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:
0x27a1440
0x27a1440
②懒汉,线程安全(加锁)
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;
③懒汉,线程安全(内部静态变量)(推荐)
根据内部静态变量唯一性,保证只有一个实例生成,并且构造函数只会在第一次调用getInstance方法是调用,保证线程安全性,此方法为最佳版
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:
Singleton@6d06d69c
Singleton@6d06d69c
②懒汉,线程安全(加锁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++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。
本章为所有设计模式的第一章,在进行编写的时候查阅了很多资料并且学到了很多,希望和大家一起分享。
如果在编写的时候出现了错误(尤其是代码部分)还希望大家积极指正。也希望有独特见解的朋友能够不吝赐教,与君共勉。