单例模式(Singleton Pattern)——设计模式中最简单,估计也是最常见的设计模式之一,属于创建型模式。一般来说,在程序的整个生命周期中,我们希望某些类有且只有一个的时候,这些类的设计就可以采用单例模式。这种类存在的原因可能是该类的创建需要消耗系统过多的资源、花费很多的时间,或者业务上客观就要求了只能有一个实例等,比如经常遇到的一个场景:应用中的配置文件读取,只在系统启动的时候读取这些配置文件,并将这些配置保存在内存中,以后在程序中使用这些配置文件信息的时候不再重新读取。
概述
单例模式——顾名思义单个实例,从名字中就能引出思考问题:怎么才能保证单个,首先肯定是该类在外部无法被实例化,将构造函数私有化(private)可以实现;但又需要唯一的一个实例,那就只能类自行能够创建,并且能够保证实例的唯一性;外部虽然不能实例化,但肯定要能访问,所以要提供该实例的全局访问方法。
所以一般来说包含下面三个要素:
- 私有的静态的实例对象 private static instance。
- 私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例) private Singleton(){}。
- 公有的、静态的、访问该实例对象的方法 public static Singleton getInstance(){}。
饿汉式
这也是最常见的一种实现方式:
public class Singleton {
// 声明即创建
private static Singleton instance = new Singleton();
// 私有的构造函数
private Singleton() {
}
// 公有的、静态的、访问该实例对象的方法
public static Singleton getInstance() {
return instance;
}
// 具体方法
public void sayHello(){
System.out.println("您好,我能为你做些什么?");
}
}
下面来测试一下:
public class Test {
public static void main(String[] args) {
// 获取实例
Singleton singleton = Singleton.getInstance();
// 调用方法
singleton.sayHello();
}
}
运行结果:
您好,我能为你做些什么?
这种方式不管类有没有被使用,先创建实例,被形象的称为饿汉式。很明显此种方式能够保证线程安全,但不能延迟初始化,当类的创建消耗资源比较少,速度比较快的时候,缺点还不明显;如果类的创建耗时且占资源,并且类不止一个的时候,不但应用启动速度会受影响,对于没用使用就占用资源也是一种不合理。
懒汉式
正因为如此,我们稍作调整便产生了能够延迟初始化的懒汉式:应用刚启动的时候,并不创建实例,当外部调用该类的实例或者方法的时候,才创建该类的实例,以时间换空间。
public class Singleton {
// 只声明不创建
private static Singleton instance = null;
// 私有的构造函数
private Singleton() {
}
// 先判断实例是否存在
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种方式的使用跟饿汉式一样,这里要问提出一个问题:当被实际使用的时候才创建实例,那如果彼时有多个线程同时调用Singleton.getInstance()方法会产生什么情况?很有可能会产生多个实例。那如果解决这个问题呢?最先想到的简单方法:加锁。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
虽然synchronized的性能JDK高版本进行了优化,但总是会牺牲一点效率,并且是为了应对应用中的小概率事件,大部分时间instance == null是不成立的。所以这里我们对整个getInstance()加锁进行方法同步是不明智的,锁的粒度太大,很多线程同时访问的时候阻塞很严重,尤其在getInstance() 的性能对应用程序很关键的时候不建议频繁使用;我们只要保证实例的创建是唯一的,可以对必要代码块进行加锁。
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这里使用了实例两次非空判断,一次在进入synchronized代码块之前,一次在进入synchronized代码块之后两次判断是为了避免,线程A在创建实例的过程中,线程B因为instance == null进入同步代码块等待创建实例,等A创建返回以后,B接着继续创建实例。那为什么不像下面这样设置同步代码块呢?
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
这样也是可以保证多线程下实例唯一,但是我们知道synchronized进行同步,意味着独占锁,同一时间只能一个线程执行同步块里面的代码,还有锁的争夺、释放等问题,是很消耗资源的。如果不在进入同步块之前进行非空判断,如果之前已经有线程创建了该类的实例,那每次的访问该实例的方法都要进入同步块,这样会非常的耗费性能。所以进入同步块之前加上了非空判断,发现实例已经存在了,就不必进入同步块了,直接实例即可,这种双重校验锁可以在线程安全且情况下保持高性能。
new Singleton()做了什么:
- 看class对象是否加载,如果没有就先加载class对象。
- 给Singleton实例分配内存,将函数压栈,并且申明变量类型。
- 初始化构造函数以及里面的字段,在堆内存开辟空间。
- 将instance对象指向分配的内存空间。
由于java编译器允许执行无序,并且jdk1.5之前的jvm(java内存模型)中的Cache,寄存器到主内存的回写顺序规定,3和4是无法保证按顺序执行的; 导致实例内存还没分配,就被使用了。
比如:线程A执行到new Singleton(),开始初始化实例对象,由于存在指令重排序,这次new操作,先把引用赋值了,还没有执行构造函数。这时时间片结束了,切换到线程B执行,线程B调用new Singleton()方法,发现引用不等于null,就直接返回引用地址了,然后线程B执行了一些操作,就可能导致线程B使用了还没有被初始化的变量。
这个问题已经修复了,SUN官方调整了JVM,加了volatile之后,就保证new 不会被指令重排序。因此在jdk1.5之前只需要写成这样既可, private volatitle static Singleton instance; 这样就可以保证每次都是从主内存中取,当然这样写或多或少的回影响性能,但是为了安全起见,这点性能牺牲还是值得
静态内部类式
上面的最优实现使用了synchronized同步块,并且用了双重非空校验,保证了懒汉式单例模式在多线程环境下的安全性和高性能,但还是想能不能再简单些,再优雅些。
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式能达到双检锁方式一样的功效,但实现更简单,利用了 classloader 机制来保证初始化 instance 时只有一个线程,不仅确保了线程的安全性,也能够保证对象的唯一性;同时只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现延迟加载。对静态域使用延迟初始化,应使用这种方式,双检锁方式可在实例域需要延迟初始化时使用。
枚举式
介绍枚举方式之前,说一个单例模式被某些方法破解的问题,例如反射破解:
public class Test {
public static void main(String[] args) {
// 获取实例
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
try {
Class clazz = Class.forName("pattern.singleton.Singleton");
Constructor c = clazz.getDeclaredConstructor();
c.setAccessible(true);
Singleton s1 = (Singleton)c.newInstance();
Singleton s2 = (Singleton)c.newInstance();
//通过反射,得到的两个不同对象
System.out.println(s1);
System.out.println(s2);
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
pattern.singleton.Singleton@2db0f6b2
pattern.singleton.Singleton@2db0f6b2
pattern.singleton.Singleton@3cd1f1c8
pattern.singleton.Singleton@3a4afd8d
从结果中可以看出通过反射可以生成多个实例,《effective java》中只简单的提了几句话:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要低于这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。”,也就是可以在构造函数中加入返回的实例对象非空判断,来防止别人通过反射创建多个实例对象。
private Singleton() {
if (instance != null){
throw new RuntimeException();
}
}
同样通过反序列化机制也可以破解单例模式。简单来说“任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”
《effective java》第77条:对于实例控制,枚举类型优于readResolve
由于 JDK1.5 之后才加入 enum 特性,这种实现方式还没有被广泛采用,不免让人感觉生疏,在实际工作中也很少用。但这是实现单例模式的最佳方法,它更简洁,自动支持序列化机制,绝对防止多次实例化。
public enum Singleton {
INSTANCE;
public void sayHello() {
System.out.println("您好,我能为你做些什么?");
}
}
测试一下:
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.sayHello();
}
}
运行结果:
您好,我能为你做些什么?
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
至于枚举为什么可以防止反射和反序列化,这里先不详述了。简单的说也就是调用反射newInstance方法时会检查是否为枚举类,如果是将报错;在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
登记式单例
Spring使用的是单例注册表方式(登记式单例),即维护一组单例类的实例,将这些实例存储到一个Map(登记簿)中,对于已经登记过的单例,则从工厂直接返回,对于没有登记的,则先登记,而后返回,这种方式并没有去改变类,所做的就是起到一个登记的作用。IOC容器就是做的这个事,你需要就找他去拿,他就可以很方便的实现Bean的管理。
看个例子:
public class RegSingleton {
// 使用一个map来当注册表
private static Map<String, Object> regMap;
// 静态块,在类被加载时自动执行,把RegSingleton自己也纳入容器管理
static {
regMap = new ConcurrentHashMap<>(32);
regMap.put(RegSingleton.class.getName(), new RegSingleton());
}
// 如果需要继承,则修改构造函数的可见性
private RegSingleton() {
}
public static Object getInstance(String name) {
if (name == null) {
name = RegSingleton.class.getName();
}
if (regMap.get(name) == null) {
try {
regMap.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return regMap.get(name);
}
}
Spring源码,…为了节约篇幅。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
...
// name 要检索的bean的名称
// requiredType 要检索的bean所需的类型
// args 使用显式参数创建bean实例时使用的参数(仅在创建新实例时应用,而不是在检索现有实例时应用)
// typeCheckOnly 是否为类型检查而获得实例,而不是实际使用
protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
// 返回bean名称,剥离工厂引用前缀,并将别名解析为规范名称
final String beanName = this.transformedBeanName(name);
// 从缓存中获取对象,调用了DefaultSingletonBeanRegistry的getSingleton方法
Object sharedInstance = this.getSingleton(beanName);
Object bean;
if (sharedInstance != null && args == null) {
if (this.logger.isDebugEnabled()) {
// 指定的singleton的bean是否正在创建
if (this.isSingletonCurrentlyInCreation(beanName)) {
this.logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
} else {
this.logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 完成FactoryBean的相关处理,并用来获取FactoryBean的处理结果
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
} else {
// 在当前线程中,返回指定的bean是否正在创建。
if (this.isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 检查该工厂是否存在bean定义
BeanFactory parentBeanFactory = this.getParentBeanFactory();
if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
// 递归寻找
String nameToLookup = this.originalBeanName(name);
if (args != null) {
return parentBeanFactory.getBean(nameToLookup, args);
}
// 如果没有args,委托给标准的getBean方法
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
if (!typeCheckOnly) {
// 将指定的bean标记为已经创建(或即将创建)
this.markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
this.checkMergedBeanDefinition(mbd, beanName, args);
// 先初始化当前bean所依赖的bean
String[] dependsOn = mbd.getDependsOn();
String[] var11;
if (dependsOn != null) {
...
}
// 单例的情况下
if (mbd.isSingleton()) {
// 从缓存(singletonObjects)中去拿
sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
// 回调函数
public Object getObject() throws BeansException {
try {
// 创建bean
return AbstractBeanFactory.this.createBean(beanName, mbd, args);
} catch (BeansException var2) {
AbstractBeanFactory.this.destroySingleton(beanName);
throw var2;
}
}
});
// 获取给定bean实例的对象,无论是bean实例本身,还是FactoryBean创建的对象
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
...
} else {
...
}
} catch (BeansException var23) {
this.cleanupAfterBeanCreationFailure(beanName);
throw var23;
}
}
// 类型检查,没有问题就返回已经创建好的bean,此时这个bean是包含依赖关系的bean
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
...
} else {
return bean;
}
}
}
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// 内部标记为一个空的单例对象:并发Map(不支持空值)作为标志值
protected static final Object NULL_OBJECT = new Object();
protected final Log logger = LogFactory.getLog(this.getClass());
// 存放singleton对象的缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
// 存放制造singleton的工厂对象的缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
// 存放singletonFactory制造出来的singleton的缓存
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
// 单例注册表
private final Set<String> registeredSingletons = new LinkedHashSet(256);
...
// 公有构造函数,可继承
public DefaultSingletonBeanRegistry() {
}
// 注册单例对象,加锁,singletonObjects存在抛出异常,不存在则添加addSingleton
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
...
}
// 添加单例对象,加锁
protected void addSingleton(String beanName, Object singletonObject) {
...
}
...
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先从singletonObjects获取
Object singletonObject = this.singletonObjects.get(beanName);
// 如果beanName对应的实例不存在但是正在创建
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized(this.singletonObjects) {
// 再从earlySingletonObjects获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 再从singletonFactory获取
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject != NULL_OBJECT ? singletonObject : null;
}
...
}
singletonObjects指单例对象的cache,singletonFactories指单例对象工厂的cache,earlySingletonObjects指提前曝光的单例对象的cache,这三个cache构成了三级缓存,容量分别为256,16,16。
getSingleton()可以看到获取顺序:singletonObjects——earlySingletonObjects——singletonFactories 。
在singletonObjects中获取bean的时候,没有使用synchronized,而在singletonFactories和earlySingletonObjects中的操作都是在synchronized代码块中完成的,正好和他们各自的数据类型对应,singletonObjects使用的使用ConcurrentHashMap线程安全,而singletonFactories和earlySingletonObjects使用的是HashMap,线程不安全。
总结
单例模式实现比较简单,有点也很明显:
- 实例只有一个,节省一定的资源。
- 提供了对唯一实例的受控访问。
- 允许可变数目的实例。
但也存在缺点:
- 大部分实现模式不能被继承。
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
- 如果单例类的职责过重,在一定程度上违背了“单一职责”原则。
- 滥用单例将带来一些负面问题。
在使用的时候要注意:
- 不适用于变化的对象,不能保存对象的状态。
- 被反射等方法破解单例的可能。
- 线程安全问题。
- 单例对象生命周期较长,要考虑内存泄漏的问题。
通用使用经验:
一般情况下,类功能比较简单,资源消耗少的时候,可以直接使用饿汉式;明确需要延迟加载的时候,可以使用双检锁方式或静态内部类方式;如果涉及到反序列化创建对象时,可以枚举方式;至于Spring的单例注册表方式,平时应用中使用的可能性较小,但其方式实现的思想值得借鉴,当需要编写工具的时候可以参考。