一、介绍
单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。一般介绍单例模式的书籍都会提到 饿汉式 和 懒汉式 这两种实现方式。但是除了这两种方式,本文还会介绍其他几种实现单例的方式,让我们来一起看看吧。、
二、应用
应用场景:spring中的bean就可以设置从单例模式
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
三、种类
懒汉模式、饿汉模式(推荐)、静态方法块(推荐),双重检验锁,单元素枚举类型(最佳方法)
1、懒汉模式(线程不安全)
这个是容易实现的,但是实现方法中不支持多线程,因为懒汉模式是在调用的时候才创建的,这时候可能存在多个线程同时调用。可在单线程下面使用
public class SingleTon{
private static SingleTon instance = null;
private SingleTon(){}
public static SingleTon getInstance(){
if(instance==null)
instance = new SingleTon();
return instance;
}
}
刚才说了懒汉模式因为不存在synchronized的加锁,会导致在多线程下出现线程不安全,所以我们可以给getInstance加synchronized,但是这样效率就会很低,因为在大多数情况下并不用做同步操作。所以不推荐使用
2、饿汉模式
前面提到的懒汉模式是在要创建实例的时候判断实例是否已经创建了来延迟实例的创建。而饿汉模式跟这个相反,它在类加载的时候就进行初始化。这个可以避免线程安全的原因是因为对象在调用之前就已经初始化了,而且只创建一份。这样就保证了线程安全。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
这种方式基于classload机制避免了多线程同步问题,而且相对于懒汉模式的加synchronized,又提高的效率,但是它会存在垃圾对象的情形,因为不管这个对象有没有使用,它都是会生成的。但相对来说,使用的还是比较多的。
3、双重效验锁(DCL,double-checked locking)
------突然发现计算机领域挺喜欢双重这种东西的。数据库的两阶段锁。分布式的提交两阶段协议。。。。
我们刚才前面提到懒汉模式为了实现线程安全要加synchronized,而饿汉模式存在垃圾对象的情形,那么综合这两者的功能,就可以实现双重效验锁的功能了。
public class Singleton {
private volatile static SingleTon instance;
private Singleton(){}
public static Singleton getInstance(){
//判断对象是否存在,在进行加锁
if(instance==null)
{
synchronize(Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
这个方法虽然实现了懒汉模式和饿汉模式的优点,但是理解起来比较复杂,顾可使用也可不使用。
4、静态内部类
实现了能跟双重锁一样的效果,但实现更加简单,对静态域使用延迟初始化,应使用这种方式而不是用双重锁的方式。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第2 种方式不同的是:第 2 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 2 种方式就显得很合理,顾推荐使用。
public class Singleton{
private static class SingleHolder(){
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingleHolder.INSTANCE;
}
}
5、单元素的枚举类型
如果上面的都是推荐使用和不推荐,那么这个一定是个大BOSS,为什么呢?因为我飘红了。。。。为什么这个重要呢,因为它既简单又安全。前面介绍的四种方法都存在一种不安全的机制。就是可以通过setAccessible方法,然后通过反射机制调用私有构造器。但是如果采用单元素枚举类型就不会存在这样的问题。这是由枚举类型锁决定的,而且还自动支持序列化机制,防止反序列化创建对象。最最最主要的是它的实现超级简单。。。
public enum Singleton{
INSTANCE;
public void SingleTon getInstance(){
return INSTANCE;
}
}
番外:前面提到spring中的bean使用单例模式,那么它是怎么样实现的呢。我们可以从源码中去找答案。Spring的依赖注入(包括lazy-init方式)都是发生在 AbstractBeanFactory 的 getBean 里。 getBean 的 doGetBean 方法调用 getSingleton 进行bean的创建。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);//首先从缓冲区查看有没有
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {//缓冲没有且没有创建过
synchronized (this.singletonObjects) {//采用双重效验锁模式进行创建对象
singletonObject = this.earlySingletonObjects.get(beanName);//从更早的缓存区查找是否存在
if (singletonObject == null && allowEarlyReference) {//不存在的时候
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
通过源码我们可以看到在创建对象的时候首先是从缓冲区中查找有没有这个对象
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
这个缓冲区采用的concurrenHashMap来存储,其中key是bean的名字,value是对象。
当缓冲区不存在的时候,加上判断对象没有创建才采用双重检验锁的方式进行创建对象。
那么如果存在多线程使用的时候怎么解决安全模式呢,这里就要涉及到一个无状态bean和有状态bean。Stateless无状态用单例Singleton模式,Stateful有状态就用原型Prototype模式。具体文章参考bean的有状态和无状态