一、概述

  保证一个类仅有一个实例,并提供一个全局访问点。单例要求:私有构造器、线程安全、延迟加载、序列化和反序列化安全、反射攻击

1.1、适用场景

  1、在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象。

  2、在整个程序空间使用全局变量,共享资源。

  3、在大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。

  想确保任何情况下都绝对只有一个实例

1.2、优缺点

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

  1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。在内存里只有一个实例,减少了内存开销,可以避免对资源的多重占用

  2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

  3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

缺点:

  1、没有接口,扩展困难

1.3、两种创建模式对比  

  饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。

  懒汉式是典型的时间换空间,延迟加载,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。

1.4、安全发布的常用模式

  可变对象必须通过安全的方式来发布,这通常意味着在发布和使用该对象的线程时都必须使用同步。如何确保使用对象的线程能够看到该对象处于已发布的状态,如何在对象发布后对其可见性进行修改。

  安全地发布一个对象,对象的应用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

  • 在静态初始化函数中初始化一个对象引用
  • 将对象的应用保存到volatile类型的域或者AtomicReferance对象中
  • 将对象的引用保存到某个正确构造对象的final类型域中
  • 将对象的引用保存到一个由锁保护的域中。

  在线程安全容器内部的同步意味着,在将对象放入到某个容器,例如Vector或synchronizedList时,将满足上述最后一条需求。如果线程A将对象X放入一个线程安全的容器,随后线程B读取这个对象,那么可以确保B看到A设置的X状态,即便在这段读/写X的应用程序代码中没有包含显式的同步。尽管Javadoc在这个主题上没有给出很清晰的说明,但线程安全库中的容器类提供了以下的安全发布保证:

  • 通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)
  • 通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程
  • 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。

类库中的其他数据传递机制(例如Future和Exchanger)同样能实现安全发布,在介绍这些机制时将讨论它们的安全发布功能。

通常,要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化器: public static Holder holder = new Holder(42);

静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布[JLS 12.4.2]。

更多推荐看:java并发编程,相关介绍 对象可见性、对象的不变性、安全发布对象

二、详细说明

  小结:一般情况下,推荐使用饿汉方式, 只有在要明确实现 lazy loading 效果时,才会使用静态内置类方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用双检锁方式。

2.1、饿汉模式【可以使用】

是否 Lazy 初始化:否;是否多线程安全:是;实现难度:

描述:这种方式比较常用,但容易产生垃圾对象。

优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

  它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

  它的特点是加载类的时候比较慢,但运行时获得对象的速度比较快。它从加载到应用结束会一直占用资源。程序初始化的时候初始化单例。

public class EagerSingleton {
//饿汉单例模式
//在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
private final static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化 private EagerSingleton() {
//私有构造函数
System.out.println("new EagerSingleton");
} //静态,不用同步(类加载时已初始化,不会有多线程的问题)
public static EagerSingleton getInstance() {
return instance;
}
}

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 100; i++) {
fixedThreadPool.submit(() -> {
EagerSingleton instance = EagerSingleton.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
System.out.println("生成类数:"+setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:"+s);
}
}

结果

new EagerSingleton
耗时:73ms
生成类数:1
hashcode:1512308980

关键字final说明

  final修饰的变量值不会改变。但是在多线程的环境中,它还会保证两点,1. 其他线程所看到的final字段必然是初始化完毕的。 2. final修饰的变量不会被程序重排序。

  Final 变量在并发当中,原理是通过禁止cpu的指令集重排序,来保证对象的安全发布,防止对象引用被其他线程在对象被完全构造完成前拿到并使用。(重排序详解http://ifeve.com/java-memory-model-1/ http://ifeve.com/java-memory-model-2/)

  对于final域,编译器和处理器要遵守两个重排序规则:

  1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
  2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

  与Volatile 有相似作用,不过Final主要用于不可变变量(基本数据类型和非基本数据类型),进行安全的发布(初始化)。而Volatile可以用于安全的发布不可变变量,也可以提供可变变量的可见性。

关键字static说明

  可以保证在一个线程未使用其他同步机制的情况下总是可以读到一个类的静态变量的初始值。

  static变量是随着类被初次访问而初始化的。在多线程的环境中,需要保证一个变量在线程中的可见性是需要对内存做一些操作指令的。
  可以简易来描述下这个过程,当两个线程A,B对一个共享变量进行操作的时候,首先是把内存中的数据加载进各自的处理器中,然后在放入各自的寄存器。当A中更新共享变量的时候,首先会被放入写缓存器中,然后再写入高速缓存中,最后一步是放进内存中,每个处理器都有各自的写缓存器和高速缓存。所以在一个时间点,线程B读取的共享变量值并不是A更新的那一个,很可能是一个旧值。在计算机系统设计中,为了保证变量的可见性,有一种协议叫做缓存一致性协议,它的大概作用就是说,不同的处理器可以读取对方高速缓存中的值。这时候我们要保证可见性只需要一步,就是当共享变量被更新的时候,原子性保证把值写入高速缓存中就可以了,volatile的实现方式也是基于这种思想。那么static变量所做的事情就是,在某个线程调用的时候,就写入内存中这个值,保证内存中必然有这个值的存在,这里注意:static并不能保证多线程之后操作的可见性。001-CPU多级缓存架构

小结

static保证了变量的初始值,final保证了不被JIT编译器重排序。对于一个单例模式来说,它所在的类在被引用的时候,static会保证它被初始化完毕,且是所有线程所见的初始化,final保证了实例初始化过程的顺寻性。两者结合保证了这个实例创建的唯一性。

2.2、懒汉模式【后创建】  

  它的特点是运行时获得对象的速度比较慢,但加载类的时候比较快。它在整个应用的生命周期只有一部分时间在占用资源。

2.2.1、方式一、synchronized锁机制

版本一、初始版本【不可以,线程不安全】

是否 Lazy 初始化:是;是否多线程安全:否;实现难度:

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class Singleton001 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton001 instance = null; /* 私有构造方法,防止被实例化 */
private Singleton001() {
System.out.println("new Singleton001");
} /* 静态工程方法,创建实例 */
public static Singleton001 getInstance() {
if (instance == null) {
instance = new Singleton001();
}
return instance;
} /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}

  会有多线程问题。

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 100; i++) {
fixedThreadPool.submit(() -> {
Singleton001 instance = Singleton001.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
System.out.println("生成类数:"+setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:"+s);
}
}

输出

new Singleton001
new Singleton001
new Singleton001
new Singleton001
耗时:72ms
生成类数:4
hashcode:158094720
hashcode:1046297923
hashcode:1303744419
hashcode:1572373644

在多线程的情况下,这样写可能会导致instance有多个实例。比如下面这种情况,考虑有两个线程同时调用getInstance()

Time Thread A Thread B
T1 检查到instance为空  
T2   检查到instance为空
T3   初始化对象A
T4   返回对象A
T5 初始化对象B  
T6 返回对象B  

可以看到,instance被实例化了两次并且被不同对象持有。完全违背了单例的初衷。

版本二、synchronized同步方法迭代【可以,但是效率比较低,不推荐,锁了方法体】

是否 Lazy 初始化:是;是否多线程安全:是;实现难度:

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

public class Singleton002 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton002 instance = null; /* 私有构造方法,防止被实例化 */
private Singleton002() {
} /* 静态工程方法,创建实例 */
public static synchronized Singleton002 getInstance() {
if (instance == null) {
instance = new Singleton002();
}
return instance;
} /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}

但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,

测试:

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 50; i++) {
fixedThreadPool.submit(() -> {
Singleton002 instance = Singleton002.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
System.out.println("生成类数:"+setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:"+s);
}
}

输出

new Singleton002
耗时:67ms
生成类数:1
hashcode:1052225082

这种实现方式的运行效率会很低。同步方法效率低。

版本三、同步代码块synchronized迭代【不可以,创建和赋值是两步操作,指令重排,解释同版本四、没锁住】

事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:

public class Singleton003 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton003 instance = null; /* 私有构造方法,防止被实例化 */
private Singleton003() {
System.out.println("new Singleton003");
} /* 静态工程方法,创建实例 */
public static Singleton003 getInstance() {
if (instance == null) {
synchronized (Singleton003.class) {
instance = new Singleton003();//
}
}
return instance;
} /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}

测试

   @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(500);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 500; i++) {
fixedThreadPool.submit(() -> {
Singleton003 instance = Singleton003.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
System.out.println("生成类数:"+setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:"+s);
}
}

输出

new Singleton003
new Singleton003
耗时:68ms
生成类数:2
hashcode:158094720
hashcode:1961726163

这样的方法进行代码块同步,代码的运行效率是能够得到提升,但是却没能保住线程的安全性。

在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。

版本四、同步代码块synchronized迭代,增加一次判断【指令重排,不可以】

public class Singleton004 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton004 instance = null; /* 私有构造方法,防止被实例化 */
private Singleton004() {
System.out.println("new Singleton004");
} /* 静态工程方法,创建实例 */
public static Singleton004 getInstance() {
if (instance == null) {
synchronized (Singleton004.class) {
if (instance == null) {
instance = new Singleton004();
}
}
}
return instance;
} /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 50; i++) {
fixedThreadPool.submit(() -> {
Singleton004 instance = Singleton004.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("生成类数:" + setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:" + s);
}
}

输出

new Singleton004
耗时:63ms
生成类数:1
hashcode:1052225082

instance = new Singleton004();实例化对象代码过程

  1. 分配内存空间
  2. 初始化对象
  3. 将对象指向刚分配的内存空间

但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:

  1. 分配内存空间
  2. 将对象指向刚分配的内存空间
  3. 初始化对象

现在考虑重排序后,两个线程发生了以下调用:

Time Thread A Thread B
T1 检查到instance为空  
T2 获取锁  
T3 再次检查到instance为空  
T4 为instance分配内存空间  
T5 将instance指向内存空间  
T6   检查到instance不为空
T7   访问instance(此时对象还未完成初始化)
T8 初始化instance  

在这种情况下,T7时刻线程B对instance的访问,访问的是一个初始化未完成的对象。

版本五、 同步代码块synchronized迭代,同时增加volatile,双锁DCL【可以】

JDK 版本:JDK1.5 起;是否 Lazy 初始化:是;是否多线程安全:是;实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

  为了解决上述问题,需要在instance前加入关键字volatile。使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

getInstance() 的性能对应用程序很关键。

public class Singleton005 {

    // 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
volatile private static Singleton005 instance = null; /* 私有构造方法,防止被实例化 */
private Singleton005() {
System.out.println("new Singleton005");
} /* 静态工程方法,创建实例 */
public static Singleton005 getInstance() {
if (instance == null) {
synchronized (Singleton005.class) {
if (instance == null) {
instance = new Singleton005();
}
}
}
return instance;
} /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 500; i++) {
fixedThreadPool.submit(() -> {
Singleton005 instance = Singleton005.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("生成类数:" + setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:" + s);
}
}

输出

new Singleton005
耗时:63ms
生成类数:1
hashcode:1961726163

2.2.2、方式二、静态内置类实现单例模式【可以】

是否 Lazy 初始化:是;是否多线程安全:是;实现难度:一般

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉方式不同的是:饿汉方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉方式就显得很合理。

public class Singleton006 {

    /* 私有构造方法,防止被实例化 */
private Singleton006() {
System.out.println("new Singleton006");
} /* 静态工程方法,创建实例 */
public static Singleton006 getInstance() {
return SingletonFactory.instance;
} //内部类
private static class SingletonFactory {
private finnal static Singleton006 instance = new Singleton006();
} //该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉
protected Object readResolve() throws ObjectStreamException {
System.out.println("调用了readResolve方法!");
return SingletonFactory.instance;
}
}

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 500; i++) {
fixedThreadPool.submit(() -> {
Singleton006 instance = Singleton006.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("生成类数:" + setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:" + s);
}
}

输出

new Singleton006
耗时:67ms
生成类数:1
hashcode:752816814

看似完美,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。

2.2.3、方式三、使用static代码块实现单例【可以】

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。

public class Singleton007 {

    private static Singleton007 instance = null;

    /* 私有构造方法,防止被实例化 */
private Singleton007() {
System.out.println("new Singleton007");
} static {
instance = new Singleton007();
} /* 静态工程方法,创建实例 */
public static Singleton007 getInstance() {
return instance;
}
}

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 500; i++) {
fixedThreadPool.submit(() -> {
Singleton007 instance = Singleton007.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("生成类数:" + setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:" + s);
}
}

输出

new Singleton007
耗时:67ms
生成类数:1
hashcode:1046297923

看似完美,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。

2.2.4、方式四、使用enum代码块实现单例【可以,推荐】

JDK 版本:JDK1.5 起;是否 Lazy 初始化:否;是否多线程安全:是;实现难度:

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用

public class Singleton008 {

    private enum MyEnumSingleton{
singletonFactory; private Singleton008 instance; private MyEnumSingleton(){//枚举类的构造方法在类加载是被实例化
instance = new Singleton008();
} public Singleton008 getInstance(){
return instance;
}
} /* 私有构造方法,防止被实例化 */
private Singleton008() {
System.out.println("new Singleton008");
} public static Singleton008 getInstance(){
return MyEnumSingleton.singletonFactory.getInstance();
}

或者单纯使用

public enum Singleton012enum {
INSTANCE; Singleton012enum() {
System.out.println("ctor Singleton012enum");
} public static Singleton012enum getInstance(){
return INSTANCE;
}
public void whateverMethod() {
System.out.println("ss");
}
}

也是可以的

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 500; i++) {
fixedThreadPool.submit(() -> {
Singleton008 instance = Singleton008.getInstance();
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("生成类数:" + setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:" + s);
}
}

输出

new Singleton008
耗时:72ms
生成类数:1
hashcode:346401817

2.2.5、方式五、线程单例

这种方式只能保证在一个线程内拿到单例对象

public class Singleton013thread {

    private static final ThreadLocal<Singleton013thread> treadLocalInstance =
new ThreadLocal<Singleton013thread>() {
@Override
protected Singleton013thread initialValue() {
return new Singleton013thread();
}
}; private Singleton013thread() {
System.out.println("ctor Singleton013thread");
} public static Singleton013thread getInstance() {
return treadLocalInstance.get();
}
}

测试

    @Test
public void getInstance() throws Exception {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 5; i++) {
fixedThreadPool.submit(() -> {
Singleton013thread instance = Singleton013thread.getInstance();
Singleton013thread instance2 = Singleton013thread.getInstance();
setObj.add(instance.hashCode());
setObj.add(instance2.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("生成类数:" + setObj.size());
Thread.sleep(1000);
for (Integer s : setObj) {
System.out.println("hashcode:" + s);
}
}

输出

耗时:57ms
生成类数:0
ctor Singleton013thread
ctor Singleton013thread
ctor Singleton013thread
ctor Singleton013thread
ctor Singleton013thread
hashcode:346401817
hashcode:421396084
hashcode:1046297923
hashcode:1425807869
hashcode:2049597926

2.2.6、使用CAS锁实现(线程安全)

利用AtomicReference

AtomicReference类提供了一个可以原子读写的对象引用变量。 原子意味着尝试更改相同AtomicReference的多个线程(例如,使用比较和交换操作)不会使AtomicReference最终达到不一致的状态。 AtomicReference甚至有一个先进的compareAndSet()方法,它可以将引用与预期值(引用)进行比较,如果它们相等,则在AtomicReference对象内设置一个新的引用。

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。
CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。

public class Singleton014Atomic {

    private static AtomicReference<Singleton014Atomic> singleton = new AtomicReference<Singleton014Atomic>();

    private Singleton014Atomic() {
} public static Singleton014Atomic getInstance() {
for (; ; ) {
Singleton014Atomic instance = singleton.get();
if (instance != null) {
return instance;
}
instance = new Singleton014Atomic();
if (singleton.compareAndSet(null, instance)) {
return instance;
}
}
}
}

三、扩展

3.1、spring实现的单例

在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例)

singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。

prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。

Spring bean 默认是单例模式。

pom依赖

        <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>

3.1.1、单例示例

基础类

public class Singleton009spring {
public Singleton009spring() {
System.out.println("ctor Singleton009spring");
} public void init() {
System.out.println("init Singleton009spring");
}
}

xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="singleton009spring" class="com.github.bjlhx15.patterns.base.create.singleton.Singleton009spring"
init-method="init" scope="singleton"></bean>
</beans>

测试

    @Test
public void test_singleton() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Singleton009spring hi1 = (Singleton009spring) context.getBean("singleton009spring");
Singleton009spring hi2 = (Singleton009spring) context.getBean("singleton009spring");
System.out.println(hi1);
System.out.println(hi2);
}

输出

ctor Singleton009spring
init Singleton009spring
com.github.bjlhx15.patterns.base.create.singleton.Singleton009spring@631330c
com.github.bjlhx15.patterns.base.create.singleton.Singleton009spring@631330c

单例,二个变量指向一个对象。

3.1.2、多例模式

java bean不变

xml配置增加

    <bean id="singleton009spring2" class="com.github.bjlhx15.patterns.base.create.singleton.Singleton009spring"
init-method="init" scope="prototype"></bean>

测试

    @Test
public void test_prototype() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Singleton009spring hi1 = (Singleton009spring) context.getBean("singleton009spring2");
Singleton009spring hi2 = (Singleton009spring) context.getBean("singleton009spring2");
System.out.println(hi1);
System.out.println(hi2);
}

输出

ctor Singleton009spring
init Singleton009spring
ctor Singleton009spring
init Singleton009spring
com.github.bjlhx15.patterns.base.create.singleton.Singleton009spring@631330c
com.github.bjlhx15.patterns.base.create.singleton.Singleton009spring@42f93a98

结论:每次访问bean,均创建一个新实例。

3.1.3、单例注册表

上述的饿汉式单例、懒汉式单例均比较遗憾不能被继承

克服前两种单例类不能被继承的缺点,我们可以使用另外一种特殊化的单例模式,它被称为单例注册表。

public class Singleton010Reg {
static private HashMap registry = new HashMap(); //静态块,在类被加载时自动执行
static {
Singleton010Reg rs = new Singleton010Reg();
registry.put(rs.getClass().getName(), rs);
} //受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点
protected Singleton010Reg() {
System.out.println("cotr Singleton010Reg");
} //静态工厂方法,返回此类的唯一实例
public static Singleton010Reg getInstance(String name) {
if (name == null||name=="") {
name = "com.github.bjlhx15.patterns.base.create.singleton.Singleton010Reg";
}
if (registry.get(name) == null) {
try {
registry.put(name, Class.forName(name).newInstance());
} catch (Exception ex) {
ex.printStackTrace();
}
}
return (Singleton010Reg) registry.get(name);
}
}

测试

    @Test
public void getInstance() {
long start = System.currentTimeMillis();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
// Set<Integer> setObj = new HashSet<>(); //线程并发问题
ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
for (int i = 0; i < 500; i++) {
fixedThreadPool.submit(() -> {
Singleton010Reg instance = Singleton010Reg.getInstance("");
setObj.add(instance.hashCode());
});
}
fixedThreadPool.shutdown();
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("生成类数:" + setObj.size());
for (Integer s : setObj) {
System.out.println("hashcode:" + s);
}
}

输出

cotr Singleton010Reg
耗时:75ms
生成类数:1
hashcode:1588170421

3.1.4、Spring单例

spring单例在注入时候使用scope控制,默认的@Controller、@Service、@Repository、@Component是单例,修改@Scope。如何实现的..

        Singleton009spring hi1 = (Singleton009spring) context.getBean("singleton009spring");

3.1.4.1、bean创建过程

从上文看到获取bean代码,可以找到BeanFactory→AbstractBeanFactory

  

AbstractBeanFactory的作用  

  • 是抽象BeanFactory的基类,同时实现了ConfigurableBeanFactory的SPI,提供了所有的功能
  • 也可以从我们定义的资源中resource中来获取bean的定义.
  • 也提供了单例bean的缓存通过他的爷爷如图中的DefaultSingletonBeanRegistry,同时提供了单例和多例和别名的定义等操作.

1、getBean方法

提供了四个重在重载,如下

//通过name获取Bean
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
//通过name和类型获取Bean
@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return doGetBean(name, requiredType, null, false);
}
//通过name和对象参数获取Bean
@Override
public Object getBean(String name, Object... args) throws BeansException {
return doGetBean(name, null, args, false);
}
//通过name、类型和参数获取Bean
public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException {
return doGetBean(name, requiredType, args, false);
}

从这四个重载方法的方法体中可以看出,他们都是通过doGetBean来实现的。所以doGetBean其实才是真正获取Bean的地方,也是触发依赖注入发生的地方。

在spring里面都是一般这样做的,获取某个东西,实际上封装了一层内部调用如上述代码的doGetBean()方法,对外几个getXXX重载,内部使用doGetXXX实现。

2、doGetBean

方法定义

@SuppressWarnings("unchecked")
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
  • name 要检索的 bean 的名称
  • requiredType 要检索的bean所需的类型
  • args 使用显式参数创建bean 实例 时使用的参数(仅在创建新实例时应用,而不是在检索现有实例时应用)
  • typeCheckOnly 是否为类型检查而获得实例,而不是实际使用

实现说明

    protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
//返回bean名称,剥离工厂引用前缀,并将别名解析为规范名称,该方法去掉别名之后的bean的name,
final String beanName = this.transformedBeanName(name); //从上面的去掉别名的beanName拿到后,从缓存中获取bean,处理已经被创建的单例模式的bean,调用了DefaultSingletonBeanRegistry的getSingleton方法
//对于此类bean的请求不需要重复的创建(singleton)
Object sharedInstance = this.getSingleton(beanName); //声明当前需要返回的bean对象
Object bean; // 如果当前获取到的sharedInstance不为null并且参数为空,则进行FactoryBean的相关处理,并获取FactoryBean的处理结果。
if (sharedInstance != null && args == null) {
if (this.logger.isDebugEnabled()) {
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 {//如果当前获取到的sharedInstance为null //在当前线程中,返回指定的prototype bean是否正在创建。如果当前的bean已经被创建,获取会失败,可能有别的bean在引用该bean
if (this.isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
} // 下面这段 是对Ioc容器中的 BeanDefinition 是否存在进行检测,先是检测当前BeanFactory中是否能够获取到,
// 如果取不到则继续到双亲容器中进行尝试获取,如果双亲还是取不到,则继续向上一级父容器中尝试获取。 // 检查该工厂是否存在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);
}
// 将指定的bean标记为已经创建(或即将创建);这里允许bean工厂优化其缓存以重复创建指定的bean。
if (!typeCheckOnly) {
this.markBeanAsCreated(beanName);
} try {
// 先根据beanName来获取BeanDefinition,然后获取当前bean的所有依赖bean,这里是通过递归调用getBean来完成,直到没有任何依赖的bean为止。
final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
////检查给定的合并bean定义,可能抛出验证异常。
this.checkMergedBeanDefinition(mbd, beanName, args);
// 保证当前bean依赖的bean的初始化。
String[] dependsOn = mbd.getDependsOn();
String[] var11;
if (dependsOn != null) {
var11 = dependsOn;
int var12 = dependsOn.length; for (int var13 = 0; var13 < var12; ++var13) {
String dep = var11[var13];
if (this.isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
} this.registerDependentBean(dep, beanName); try {
//递归处理依赖bean
this.getBean(dep);
} catch (NoSuchBeanDefinitionException var24) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", var24);
}
}
}
//下面是创建singleton bean
if (mbd.isSingleton()) {
// 下面这段就是创建一个bean实例;这里通过调用getSingleton方法来创建一个单例bean实例;
// 从 代码 中可以看到,getSingleton的调用是通过getObject这个回调函数来间接调用createBean完成的。
sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
//回调函数getObject
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);
}
//下面是创建 多例模式 prototype bean
else if (mbd.isPrototype()) {
var11 = null; Object prototypeInstance;
try {
this.beforePrototypeCreation(beanName);
//创建prototype bean
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
} bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
//获取bean的作用域
String scopeName = mbd.getScope();
Scope scope = (Scope) this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
} try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
AbstractBeanFactory.this.beforePrototypeCreation(beanName); Object var1;
try {
var1 = AbstractBeanFactory.this.createBean(beanName, mbd, args);
} finally {
AbstractBeanFactory.this.afterPrototypeCreation(beanName);
} return var1;
}
});
bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException var23) {
throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", var23);
}
}
} catch (BeansException var26) {
this.cleanupAfterBeanCreationFailure(beanName);
throw var26;
}
} //最后是对创建的bean进行类型检查,没有问题就返回已经创建好的bean;此时这个bean是包含依赖关系的bean
//检查类型与实际的bean的是否匹配
if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
try {
return this.getTypeConverter().convertIfNecessary(bean, requiredType);
} catch (TypeMismatchException var25) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", var25);
} throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
} else {
return bean;
}
}

上面代码是spring如何获取bean的过程,实际最核心的是createBean() 方法,该方法真正的创建bean的过程

3、createBean

    /**
* 该方法是AbstractAutowireCapableBeanFactory中的实现方法
*
*/
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException { if (logger.isTraceEnabled()) {
logger.trace("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
//解析bean的class
//1.这里当解析的resolvedClass和mbd.hasBeanClass()为falsembd.getBeanClassName()同时成立时
//2.这里需要我们new一个合并的beanDefinition类,同时设置class类型
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
} //覆盖了RootBeanDefinition的prepareMethodOverrides方法
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
} try {
//获取一个解析之后的代理bean的实例,而不是真正的bean实例
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
} try {
//这里才是真正的执行创建bean的方法
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}

按照spring玩法,会有一个doCreateBean真正实现,先看createBean方法

4、doCreateBean

    /**
* 该方法是真正的创建bean的方法
* @param beanName 要创建bean的名称
* @param mbd 定义bean的beanDefinition文件
* @param args 创建bean时,构造方法所需的参数或者是调用bean工厂时来创建bean所需的参数
* @return 完成创建之后的bean的实例
* @throws BeanCreationException
*/
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException { //实例化bean的过程
BeanWrapper instanceWrapper = null;
//如果是单例,从factoryBeanInstanceCache中移除相应的bean实例
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
//创建实例
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//获取封装实例
final Object bean = instanceWrapper.getWrappedInstance();
//获取对应实例的class类型
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
//将获取到的bean类型赋值给mbd的resolvedTargetType
//resolvedTargetType用来保存真实类型bean的类型
mbd.resolvedTargetType = beanType;
} // Allow post-processors to modify the merged bean definition.
//允许后置处理修改mbd
synchronized (mbd.postProcessingLock) {
//postProcessed默认为false,故不能修改,这里取反表示可以修改
if (!mbd.postProcessed) {
try {
//对bean进行后置处理
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
} //处理循环引用和bean的生命周期
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
        //添加单例 至 单例工厂
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
} //初始化bean实例
Object exposedObject = bean;
try {
//给bean的实例填充属性
populateBean(beanName, mbd, instanceWrapper);
//初始化bean的实例
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
//引用问题处理,这里先暴露早期单例引用的bean
if (earlySingletonExposure) {
//从缓存注册中获取,这里不允许早期引用的创建
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
} //注册bean
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
} return exposedObject;
}

查看注册bean的registerDisposableBeanIfNecessary

    /**
* AbstractBeanFactory实现 注册bean
* @param beanName
* @param bean
* @param mbd
*/
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
AccessControlContext acc = System.getSecurityManager() != null ? this.getAccessControlContext() : null;
if (!mbd.isPrototype() && this.requiresDestruction(bean, mbd)) {
if (mbd.isSingleton()) {
// 单例注册
this.registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, this.getBeanPostProcessors(), acc));
} else {
Scope scope = (Scope)this.scopes.get(mbd.getScope());
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
} scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(bean, beanName, mbd, this.getBeanPostProcessors(), acc));
}
}
}

查看registerDisposableBean

    public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized(this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}

其中:disposableBeans是

    private final Map<String, Object> disposableBeans = new LinkedHashMap();

3.1.4.2、单例获取

Object sharedInstance = this.getSingleton(beanName);

核心方法

    //DefaultSingletonBeanRegistry
public Object getSingleton(String beanName) {
return this.getSingleton(beanName, true);
} protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//去map读取 private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized(this.singletonObjects) {
//去map读取 private final Map<String, Object> earlySingletonObjects = new HashMap(16);
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 去工厂 获取工厂
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 工厂获取对象 放到 earlySingletonObjects 从工厂移除
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
} return singletonObject != NULL_OBJECT ? singletonObject : null;
}

1、singletonObjects如何被添加注册单例MAP上的

可以在上述创建bean代码中看到,如果是单例

                if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
return AbstractBeanFactory.this.createBean(beanName, mbd, args);
} catch (BeansException var2) {
AbstractBeanFactory.this.destroySingleton(beanName);
throw var2;
}
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

2、这里的getSingleton如下

    //DefaultSingletonBeanRegistry
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized(this.singletonObjects) {
// 去注册MAP获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//…………
this.beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = this.suppressedExceptions == null;
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet();
} try {
//工厂获取
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException var16) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw var16;
}
} catch (BeanCreationException var17) {
BeanCreationException ex = var17;
if (recordSuppressedExceptions) {
Iterator var8 = this.suppressedExceptions.iterator(); while(var8.hasNext()) {
Exception suppressedException = (Exception)var8.next();
ex.addRelatedCause(suppressedException);
}
} throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
} this.afterSingletonCreation(beanName);
}
//添加到单例注册MAP
if (newSingleton) {
this.addSingleton(beanName, singletonObject);
}
} return singletonObject != NULL_OBJECT ? singletonObject : null;
}
}

3、对于添加addSingleton,以及singletonObjects,比较简单如下

    protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject != null ? singletonObject : NULL_OBJECT);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}

4、 工厂获取

在第1段代码,第2段代码,均有

singletonObject = singletonFactory.getObject();

第1段调用属性

  ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

第2段是参数 传入

  但是均有一个singletonFactories

通过代码可以看到 DefaultSingletonBeanRegistry 添加了单例工厂

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized(this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
} }
}

可以参看上述 doCreateBean 代码中的

        //处理循环引用和bean的生命周期
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
        //添加单例 至 单例工厂
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

3.1.4.3、Spring单例过程

1、getBean:客户调用:Singleton009spring hi1 = (Singleton009spring) context.getBean("singleton009spring");

  内部调用的是:org.springframework.beans.factory.BeanFactory#getBean(java.lang.String)

  具体实现是:org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)

2、doGetBean:上述getBean实际调用doGetBean

2.1、执行1、getSingleton:调用:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)

  →:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

    内部会查询:singletonObjects = new ConcurrentHashMap(256);

        earlySingletonObjects = new HashMap(16);

      上述均没有会从工厂获取:(ObjectFactory)this.singletonFactories.get(beanName);

      工厂没有的话放弃单例查询,

2.2、执行二、如果是单例会调用 :sharedInstance = this.getSingleton

    :org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

    参数:第2个入参:AbstractBeanFactory.this.createBean(beanName, mbd, args);

      执行:org.springframework.beans.factory.support.AbstractBeanFactory#createBean

      实际调用:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])

        内部调用:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

          内部调用:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory,此时DefaultSingletonBeanRegistry含有了singletonFactories = new HashMap(16);

    getSingleton内部执行:  

      singletonObject = singletonFactory.getObject();

      添加:this.addSingleton(beanName, singletonObject);,添加至 singletonObjects = new ConcurrentHashMap(256);

  第一次执行完毕会返回此bean

2.3、上述2.1中,内部查询

  是singletonObjects,会去取出单例

3.1.4.4、验证spring webmvc中@Controller,@Service等的单例

POM

        <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.17.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.17.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

代码Service

@Service
public class Singleton012springService {
public Singleton012springService() {
System.out.println("ctor Singleton012springService");
} public void init() {
System.out.println("init Singleton012springService");
} private String name; public String getName() { System.out.println("Service hashcode:"+this.hashCode());
return name;
} public void setName(String name) {
this.name = name;
} public String methodSetName(String name){
setName(name);
return getName();
}
}

代码Controller

@Controller
public class Singleton012springController {
@Autowired
private Singleton012springService service; private String add; public String getAdd() {
return add;
} public void setAdd(String add) {
this.add = add;
} @RequestMapping("test")
public String test(String id){
if(id.equals("1")){
setAdd("京东总部");
service.methodSetName("李宏旭");
}
System.out.println("Controller 属性:"+getAdd());
System.out.println("Controller hashcode:"+this.hashCode()); String name = service.getName();
System.out.println("Service 属性:"+name); return "OK";
}
}

xml

 <context:component-scan base-package="com.github.bjlhx15.patterns.base.create.singleton.service"></context:component-scan> 

单元测试

@RunWith(SpringJUnit4ClassRunner.class)  //使用junit4进行测试
@ContextConfiguration({"classpath:applicationContext-012.xml"}) //加载配置文件
//加上 @WebAppConfiguration
@WebAppConfiguration
public class Singleton012springControllerTest { @Autowired
private WebApplicationContext webApplicationContext; private MockMvc mockMvc; //方法执行前初始化数据
@Before
public void setUp() throws Exception {
//No qualifying bean of type 'org.springframework.web.context.WebApplicationContext' available
//加上 @WebAppConfiguration
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
} @Test
public void testControlersingleton() throws Exception {
MockHttpServletRequestBuilder mockHttpServletRequestBuilder = MockMvcRequestBuilders.get("/test");
mockHttpServletRequestBuilder.param("id", "1"); //要传入的参数
ResultActions resultActions = mockMvc.perform(mockHttpServletRequestBuilder);
resultActions.andExpect(status().isOk());
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
fixedThreadPool.submit(() -> {
MockHttpServletRequestBuilder mockHttpServletRequestBuilder1 = MockMvcRequestBuilders.get("/test");
mockHttpServletRequestBuilder1.param("id", "2"); //要传入的参数
try {
ResultActions resultActions1 = mockMvc.perform(mockHttpServletRequestBuilder1);
resultActions1.andExpect(status().isOk());
} catch (Exception e) {
e.printStackTrace();
}
});
}
fixedThreadPool.shutdown();

输出

Service hashcode:1464555023
Controller 属性:京东总部
Controller hashcode:195381554
Service hashcode:1464555023
Service 属性:李宏旭
Controller 属性:京东总部
Controller hashcode:195381554
Service hashcode:1464555023
Service 属性:李宏旭
//……

从Controller和Service的Hashcode可以看出,是单例模式

3.1.4.5、SpringMVC默认单例模式如何保证性能、以及多线程数据唯一性的

  spring对象是单例的,但类里面方法对每个线程来说都是独立运行的,不存在多线程问题,只有成员变量有多线程问题,所以方法里面如果有用到成员变量就要考虑用安全的数据结构。对于成员变量的操作,可以使用ThreadLocal来保证线程安全

  方法都是独立的,每个用户在访问的时候单独开辟了空间,而成员变量却是共有的,所有用户都是调用的同一个。每个方法中对局部变量的操作都是在线程自己独立的内存区域内完成的,所以是线程安全的。

3.2、JDK源码中的单例-Runtime

  通过查看java.lang.Runtime静态成员变量currentRuntime、getRunTime()方法、私有构造器,可知是一个单例模式的饿汉式。

public class Runtime {
private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() {
return currentRuntime;
} /** Don't let anyone else instantiate this class */
private Runtime() {}
//……
}

3.3、AbstractFactoryBean——懒汉式

org.springframework.beans.factory.config.AbstractFactoryBeangetObject() 方法中,查看调用getEarlySingletonInstance()

    public final T getObject() throws Exception {
if (this.isSingleton()) {
return this.initialized ? this.singletonInstance : this.getEarlySingletonInstance();
} else {
return this.createInstance();
}
}

使用了懒汉式,初始化单例对象

    private T getEarlySingletonInstance() throws Exception {
Class<?>[] ifcs = this.getEarlySingletonInterfaces();
if (ifcs == null) {
throw new FactoryBeanNotInitializedException(this.getClass().getName() + " does not support circular references");
} else {
if (this.earlySingletonInstance == null) {
this.earlySingletonInstance = Proxy.newProxyInstance(this.beanClassLoader, ifcs, new AbstractFactoryBean.EarlySingletonInvocationHandler());
} return this.earlySingletonInstance;
}
}

   



002-创建型-03-单例模式(Singleton)【7种】、spring单例及原理的更多相关文章

  1. 设计模式01 创建型模式 - 单例模式(Singleton Pattern)

    参考 [1] 设计模式之:创建型设计模式(6种) | 博客园 [2] 单例模式的八种写法比较 | 博客园 单例模式(Singleton  Pattern) 确保一个类有且仅有一个实例,并且为客户提供一 ...

  2. &quot;围观&quot;设计模式(7)--创建型之单例模式(Singleton Pattern)

    单例模式,也叫单子模式,是一种经常使用的软件设计模式.在应用这个模式时,单例对象的类必须保证仅仅有一个实例存在. 很多时候整个系统仅仅须要拥有一个的全局对象.这样有利于我们协调系统总体的行为.比方在某 ...

  3. 创建型模式之Singleton模式

    单例模式大概是最直观的一种设计模式了,尽管直观却不简单. 数学与逻辑学中,singleton定义为“有且仅有一个元素的集合”, 单例模式可以如下定义:“一个类有且仅有一个实例,并且自行实例化向整个系统 ...

  4. php开发面试题---创建型设计模式1(创建型设计模式有哪几种)

    php开发面试题---创建型设计模式1(创建型设计模式有哪几种) 一.总结 一句话总结: 共五种:(简单工厂模式).工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 1.学设计模式最好的方 ...

  5. [转]单例模式——C++实现自动释放单例类的实例

    [转]单例模式——C++实现自动释放单例类的实例 http://www.cnblogs.com/wxxweb/archive/2011/04/15/2017088.html http://blog.s ...

  6. [19/04/22-星期一] GOF23_创建型模式(单例模式)

    一.概念 <Design Patterns: Elements of Reusable Object-Oriented Software>(即后述<设计模式>一书),由 Eri ...

  7. 六个创建模式之单例模式(Singleton Pattern)

    定义: 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.三个特点:一个类只有一个实例:必需自己创建这个实例:必需自行向整个系统提供这个实例. 结构图: Singleton:单例类,提 ...

  8. python设计模式---创建型之单例模式

    数据结构和算法是基本功, 设计模式是最佳实现. 作为程序员,必须有空了就练一练哈. # coding = utf-8 """ # 经典单例 class Singleton ...

  9. [Android面试题-7] 写出一个Java的Singleton类(即单例类)

    1.首先明确单例的概念和特点: a>单例类只能有一个实例 b>单例类必须自己创建一个自己的唯一实例 c>单例类必须为其他所有对象提供这个实例 2.单例具有几种模式,最简单的两种分别是 ...

随机推荐

  1. Python3 acm基础输入输出

    案例一:输入字符串分割并转化成多个int数值 a, b= map(int, input().split()) try: while True: a, b= map(int, input().split ...

  2. jquery-deferred应用

    我们说jquery1.5之后用的用deferred,那么deferred到底是个什么东西,看个例子 var wait = function(){ var task = function(){ cons ...

  3. 2星|项立刚《5G时代》:资料堆砌和一些假想设想,信息浓度太低

    “ 这是一本关于5G的书,但着眼点不是要说清楚5G的技术,因为解读5G技术的图书已经有很多,我自己也不是技术专家.本书是希望探讨在一个全新的网络体系下产业的发展与改变,以及5G对社会与经济的影响.P6 ...

  4. Ignatius and the Princess IV (简单DP,排序)

    方法一:    直接进行排序,输出第(n+1)/2位置上的数即可. (容易超时,关闭同步后勉强卡过) #include<iostream> #include<cstdio> # ...

  5. KaTex语法说明

    参考链接: https://katex.org/docs/supported.html https://github.com/KaTeX/KaTeX/blob/master/docs/supporte ...

  6. idea启动springboot项目报Error running 'ServiceStarter': Command line is too long. Shorten command line for ServiceStarter or also for Application

    解决办法:在.idea文件夹下面的workspace.xml中的 <component name="PropertiesComponent">标签下面添加: <p ...

  7. mysql类型为varchar double类型字符串求和多出多个小数

    -- 错误 SELECT SUM(price) FROM m_user -- 正确 SELECT TRUNCATE ( ) FROM m_user u; -- 正确 SELECT ) ) FROM m ...

  8. 2019 杭电多校第八场 HDU - 6665 Calabash and Landlord 两矩形分平面

    题意 给出两个矩形,问这两个矩形把平面分成了几部分. 分析 不需要什么高级技能,只需 “简单” 的分类讨论. (实在太难写了,对拍找出错误都不想改 推荐博客,其中有个很好的思路,即只讨论答案为2,3, ...

  9. BZOJ4543 Hotel加强版(长链剖分)

    题意 求一棵树上,两两距离相等的三个点的三元组(无序)的个数. 题解 转自 CaptainHarryChen 的博客 CODE 代码中的f,gf,gf,g对应题解中的num,waynum,waynum ...

  10. BZOJ 2277 strongbox (gcd)

    题意 有一个密码箱,0到n-1中的某些整数是它的密码. 且满足,如果a和b都是它的密码,那么(a+b)%n也是它的密码(a,b可以相等) 某人试了k次密码,前k-1次都失败了,最后一次成功了. 问:该 ...