一、单例模式的定义及使用场景

模式定义:保证一个类只有一个实例,并且提供一个全局访问点。

场景:重量级的对象,不需要多个实例,如线程池,数据库连接池。

二、单例模式的实现方式

1.懒汉模式:延迟加载,只有在真正使用的时候,才开始实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class LazySingleton{

private volatile static LazySingleton lazySingleton;
private LazySingleton() {}

public static LazySingleton getInstance(){
if (null == lazySingleton){
// double check
synchronized (LazySingleton.class){
if (null == lazySingleton){
// 字节码层
// JIT,CPU
// 1.分配空间
// 2.引用赋值
// 3.初始化
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}

1)线程安全问题

2)double check,枷锁优化

3)编译器(JIT),CPU有可能对指令进行重排序,导致使用到未初始化的实例,可以通过添加volatile关键字进行修饰,对于volatile修饰的字段,可以防止指令重拍。

2.饿汉模式:类加载的初始化阶段就完成了实例的初始化,本质上就是借助于jvm类加载机制,保证实例的唯一性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class HungrySingleton {

private static HungrySingleton hungrySingleton = new HungrySingleton();

private HungrySingleton(){
if (HungrySingleton.getInstance()!=null){
throw new RuntimeException("单例模式不允许多个实例存在!!!");
}
}

public static HungrySingleton getInstance(){
return hungrySingleton;
}
}

类加载过程:

1)加载二进制数据到内存中,生成对应的Class数据结构

2)连接:a.验证,b.准备(给类的静态成员变量赋默认值),c.解析

3)初始化:给类的静态变量赋值

只有在真正使用对应的类时,才会触发初始化(当前类时启动类即main函数所在类,直接new操作,访问静态属性,访问静态方法,用反射访问类,初始化一个类的子类等)。

3.静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class InnerClassSingleton{

private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}

private InnerClassSingleton (){
if (InnerClassHolder.instance!=null){

throw new RuntimeException("单例模式不允许多个实例存在!!!");
}
}

public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}

1)本质上是利用类的加载机制来保证线程安全

2)只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。

4.枚举类型

1
2
3
4
5
6
7
8
public enum EnumSingleton {

INSTANCE();

public void print(){
System.out.println(INSTANCE.hashCode());
}
}

1)天然不支持反射创建的实例,且有自己的反序列化机制

2)利用类加载自己保证线程安全

5.反射攻击实例

1
2
3
4
5
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
InnerClassSingleton instance = InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton==instance);

6.序列化

对象需要实现

1
2
3
4
5
6
7
8
class InnerClassSingleton implements Serializable {

private static final long serialVersionUID = 1324250553112887638L;

Object readResolve() throws ObjectStreamException{
return InnerClassHolder.instance;
}
}

1)可以利用指定方法来替换从反序列化流中的数据 如下:

1
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

Spring源码应用

1
2
3
4
5
6
7
8
9
// Spring && JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry
// Tomcat
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
// 反序列化指定数据源
java.util.Currency