一、单例模式的概念
单例模式是设计模式的一种,那么设计模式又是什么呢?
设计模式(Design pattern)
代表了最佳的实践,是软件开发人员在软件开发过程中面临的一般问题的解决方案,这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化,是软件工程的基石脉络,如同大厦的结构一样。
设计模式共有23种,这里只讨论最常用的单例模式:
单例模式是一种常用的软件设计模式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只创建单个对象。单例类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的要点:
1. 单例类只能有一个实例;
2. 单例类必须自行创建这个实例;
3. 单例类必须自行向其他对象提供这个实例。
具体实现时:
1. 单例模式的类只提供私有的构造函数;
2. 类定义中含有一个该类的静态私有对象;
3. 该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
二、单例模式的简单介绍:
-
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
要解决的问题:一个全局使用的类频繁地创建与销毁。
-
使用场景:当用户想控制实例数目,节省系统资源时。
-
解决方法:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
-
优点:
1). 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存);
2). 避免对资源的多重占用(比如写文件操作)。
- 缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部辑,而不关心外面怎么样来实例化。
- 使用场景:
1). 要求生产唯一序列号。
2).WEB
中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3). 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
三、单例模式的实现方式
- 懒汉式
懒汉式又分为多线程安全和多线程不安全。
1). 多线程安全:
这种方式能够在多线程中很好的工作,但效率很低,99% 情况下不需要同步。
优点:第一次调用时才初始化,避免内存浪费。
缺点:必须加锁synchronized
(同步)才能保证单例,但加锁会影响效率。
getInstance()
的性能对应用程序不是很关键(该方法使用不太频繁)。
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) {
instance = new Singleton();
}
return instance;
}
}
- 饿汉式:
这种方式比较常用,但容易产生垃圾对象。
它基于classloader
机制避免了多线程的同步问题,不过,instance
在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance
方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance
显然没有达到lazy loading
的效果。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
- 双检锁/双重校验锁(
DCL
,即double-checked locking
):
这种方式采用双锁机制,安全且在多线程情况下能保持高性能(但这种方法较复杂,不推荐使用,我也是在网上查到的,只学了个皮毛。)。getInstance()
的性能对应用程序很关键。
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;
}
}
综上,是比较常见的几种单例模式,这其中常用的是懒汉式和饿汉式。
两者的区别在于:建立单例对象的时间不同。
懒汉式:在真正用到的时候才去创建单例对象;
饿汉式:不管将来用不用,程序启动时就创建出单例对象。
使用时如何抉择?
如果单例对象构造十分耗时或者占用很多资源,比如加载插件,初始化网络连接等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。