简介
单例模式属于创建型模式的一种。
英文单词Singleton的数学含义是"有且仅有一个元素的集合"。
从实现层面看,由类自身管理自己的唯一对象,这个类提供了访问该对象的方式,可以直接访问,不需要实例化(使用new)。
动机
设计模式中的Singleton的目的是使类的对象在应用程序中保持唯一,这在某些应用场合非常重要,比如文件系统的资源管理器,又比如应用的日志,应用程序配置等。保持唯一实例有利于节约系统资源,同时提升了应用程序性能,避免对资源的多重占用。
实现
最简单实现(线程安全)-饿汉模式
public class Singleton{
private static Singleton onlyOneInstance = new Singleton();
private Singleton(){
}
public static Singleton getOnlyOneInstance(){
return onlyOneInstance;
}
}
这个实现是线程安全的,但同时也没有延迟加载带来的节约资源的好处。
简单实现(线程不安全)-懒汉模式
public class Singleton{
private static Singleton onlyOneInstance;
private Singleton(){
}
public static Singleton getOnlyOneInstance(){
if(onlyOneInstance == null){
onlyOneInstance = new Singleton();
}
return onlyOneInstance;
}
}
如果有多个线程同时运行到if(onlyOneInstance == null)
,并且此时实例为null,那多个线程会执行onlyOneInstance = new Singleton();
语句,导致实例化多次。
简单实现(线程安全)-懒汉模式
public static synchronized Singleton getOnlyOneInstance(){
if(onlyOneInstance == null){
onlyOneInstance = new Singleton();
}
return onlyOneInstance;
}
在方法getOnlyOneInstance()
上增加同步修饰符synchronized
,这样可以保证同一时间只有一个线程访问方法,从而确保不会发生多次实例化。
问题是多个线程访问该方法时会发生阻塞,导致其他线程会在该方法上等待,因此性能上有损耗。
双重校验锁(线程安全)
public class Singleton{
private volatile static Singleton onlyOneInstance;
private Singleton(){
}
public static Singleton getOnlyOneInstance(){
if(onlyOneInstance == null){
synchronized(Singleton.class){
if(onlyOneInstance == null){
onlyOneInstance = new Singleton();
}
}
}
return onlyOneInstance;
}
}
与第三点的代码比较,本方法将同步锁放在了方法内部,这样可以减少部分线程阻塞,因为在第一个判断if(onlyOneInstance == null)
时如果实例不为null,方法就返回了。
与第二点的代码比较,多个线程依然可能会同时进入到if(onlyOneInstance == null)
,同样的,此时onlyOneInstance有可能是null,因此需要在synchronized(Singleton.class)
内部再次判断if(onlyOneInstance == null)
onlyOneInstance 使用 volatile 修饰的必须的。解释这点的原因需要深入到 JVM 的指令重排机制。onlyOneInstance = new Singleton();
的执行需要分三步:
1. 分配内存
2. 初始化对象
3. 将 onlyOneInstance 指向分配的内存地址
由于 JVM 的指令重排特性,上述三步步骤可能会重排为 1>3>2 ,在多线程环境下访问onlyOneInstance时,有可能会得到一个未被初始化的实例。而volatile关键字可以阻止 JVM 的指令重排,从而保证多线程环境下正常运行。