单例模式
一、单例模式定义
确保某一个类只有一个实例,且能自行实例化并向整个系统提供这个实例。
1. 使用场景
-
数据库连接池:在一个应用中,通常需要频繁地连接数据库,如果每个模块都创建一个数据库连接对象,会造成资源浪费和不一致的问题。使用单例模式可以保证整个应用中只有一个数据库连接池对象,避免了这个问题。
-
线程池:在一个应用中,通常需要创建大量的线程来处理任务,如果每个任务都创建一个新的线程,会造成系统资源浪费和不稳定的问题。使用单例模式可以保证整个应用中只有一个线程池对象,避免了这个问题。
-
缓存系统:在一个应用中,通常需要频繁地使用缓存来提高性能,如果每个模块都创建一个缓存对象,会造成资源浪费和不一致的问题。使用单例模式可以保证整个应用中只有一个缓存对象,避免了这个问题。
-
总之,单例模式适用于需要全局唯一、频繁使用的对象场景,可以避免资源浪费和不一致的问题,提高应用的性能和稳定性。
2. 代码实现(DCL)
这里以双重校验锁DCL(Double-Checked Locking)为例
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private volatile static Singleton singleton = null;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
3. 扩展
3.1 为什么 getInstance() 方法内需要使用两个 if (singleton == null) 进行判断呢?
假设高并发下,线程A、B 都通过了第一个 if 条件。若A先抢到锁,new 了一个对象,释放锁,然后线程B再抢到锁,此时如果不做第二个 if 判断,B线程将会再 new 一个对象。使用两个 if 判断,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。
3.2 volatile 关键字的作用?
首先 singleton = new Singleton() 这段代码其实不是原子性的操作,至少分为以下3个步骤:
- 给singleton对象分配内存空间
- 调用Singleton类的构造函数等,初始化singleton对象
- 将singleton对象指向分配的内存空间
这步一旦执行了,那singleton对象就不等于null了,这里还需要知道一点,就是有时候JVM会为了优化,需要做指令重排序的操作,这里的指令,指的是CPU层面的。正常情况下,singleton = new Singleton() 的步骤是按照 1->2->3 这种步骤进行的,但是一旦JVM做了指令重排序,那么顺序很可能编程 1->3->2,如果是这种顺序,可以发现,在3步骤执行完 singleton 对象就不等于 null,但是它其实还没做步骤二的初始化工作,但是另一个线程进来时发现,singleton不等于null了,就这样把半成品的实例返回去,调用是会报错的。
4. 小结
-
单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。
-
单例模式的核心思想是通过限制类的实例化过程,确保在整个应用程序中只有一个实例对象存在。这种限制可以通过私有化类的构造函数来实现,使得外部无法直接实例化该类。
-
单例模式有两种常见的实现方式:饿汉式和懒汉式。饿汉式是指在类加载时就创建一个实例对象,并在类的静态变量中保存该实例对象。懒汉式是指在第一次使用时才创建实例对象,并在类的静态方法中进行延迟初始化。
-
使用单例模式可以确保系统中某个类只有一个实例,从而节省了系统资源,并提供了一个全局访问点,方便其他对象访问该实例。单例模式可以应用于需要共享资源的情况,例如数据库连接池、线程池等。
-
然而,单例模式也有一些缺点。由于实例对象是全局唯一的,因此单例模式会增加代码的耦合性和复杂度。单例模式也可能面临并发访问的问题,需要在实现中考虑线程安全性。
-
总结来说,单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。它可以节省系统资源并提供方便的访问方式,但也需要考虑代码的复杂性和线程安全性。