一、模式定义:
保证一个类只有一个实例,并且提供一个全局访问点
二、场景:
重量级的对象,不需要多个实例,如线程池,数据库连接池。
三、实现方法
1.懒汉模式:延迟加载, 只有在真正使用的时候,才开始实例化。
1)线程安全,加synchronized锁来控制只有一个实例进行第一次的初始化
2)double check 对加锁进行优化,只有instance == null时才加锁,其余的时候都不加锁的
3)编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰,防止指令重排。
/**
* 懒汉加载模式,用到的时候才做实例化
* */
class LazySingleton {
//加volatile可以禁止cpu的指令重排
public volatile static LazySingleton instance;
public LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
//为什么把锁加在这里而不是在加方法名上呢?如果synchronized加在方法名上那就每一次获取实例都加锁了,性能降低了,我们其实只需要确保多线程下第一次创建的时候只有一个人来实例化就可以了
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
2.饿汉模式
是立即加载的方式,无论是否会用到这个对象,都会加载。
在类加载的初始化阶段就完成了实例的初始化 。
本质上就是借助于jvm类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。
类加载过程:
1,加载二进制数据到内存中, 生成对应的Class数据结构,
2,连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
3,初始化: 给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化 如( 当前类是启动类即main函数所在类,直接进行new 操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等.)
/**
* 饿汉加载模式
* */
class HungrySingleton{
private static HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance() {
return instance;
}
}
懒汉式和饿汉式的使用场景:
懒汉式
是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量。
使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
饿汉式
如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿,还有就是有一些可能不需要用到的类也直接就加载了浪费了内存空间。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
3.静态内部类
1).本质上是利用类的加载机制来保证线程安全
2).只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一
种形式。
/**
* 静态内部类模式
*/
class InnerClassSingleton {
private static class InnerClassHolder {
private InnerClassHolder(){
System.out.println("33333333333333");
}
static {
System.out.println("444444444444444");
}
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton() {
System.out.println("222222222222");
}
public static InnerClassSingleton getInstance() {
System.out.println("111111111111111");
return InnerClassHolder.instance;
}
}
4.反射攻击实例:
Constructor<InnerClassSingleton> declaredConstructor=InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible( true );
InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance();
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton==instance);
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;
}
}
5.枚举类型
1)天然不支持反射创建对应的实例,且有自己的反序列化机制
2)利用类加载机制保证线程安全
class EnumSingleton{
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
}
6.序列化
1)可以利用 指定方法来替换从反序列化流中的数据 如下
ANY‐ACCESS‐MODIFIER Object readResolve() throws ObjectStreamException;
class InnerClassSingleton implements Serializable {
static final long serialVersionUID = 42L;
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;
}
Object readResolve() throws ObjectStreamException {
return InnerClassHolder.instance;
}
}
应用场景:
重量级的对象,不需要多个实例,如线程池,数据库连接池。
源码中的应用 :
// Spring & JDK
2 java.lang.Runtime
3 org.springframework.aop.framework.ProxyFactoryBean
4 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
5 org.springframework.core.ReactiveAdapterRegistry
6 // Tomcat
7 org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
8 // 反序列化指定数据源
9 java.util.Currency