抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

单例设计模式(Singleton)

知识点

  1. 模式定义/应用场景/类图分析
  2. 字节码知识/字节码指令重排序
  3. 类加载机制
  4. JVM序列化机制
  5. 单例模式在Spring框架 & JDK源码中的应用

模式定义

保证一个类只有一个实例,并且提供一个全局的访问点

使用场景

重量级对象。不需要单个实例,如线程池,数据库连接池

实现方式

1.懒汉模式

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

存在问题:

  • 线程安全问题
  • double check 加锁优化
  • 编译器(JIT),CPU 有可能对指令进行重排序,导致使用到尚未初始化 的实例,可以通过添加volatile 关键字进行修饰,

对于volatile 修饰的字段,可以防止指令重排。(具体原理后续文章展开)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class LazySingleton{ 
private volatile static LazySingleton instance;
private LazySingleton(){

}
public static LazySingleton getInstance() {
if (instance==null){
synchronized (LazySingleton.class){
if (instance==null){
instance=new LazySingleton();
// 字节码层
// JIT , CPU 有可能对如下指令进行重排序
// 1 .分配空间
// 2 .初始化
// 3 .引用赋值
// 如重排序后的结果为如下
// 1 .分配空间
// 3 .引用赋值 如果在当前指令执行完,有其他线程来获取实例,将拿到尚未初始化好的实例
// 2 .初始化
}
}
}
return instance;
}
}

2.饿汉模式:

类加载的初始化阶段就完成了实例的初始化。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。

类加载过程:

  1. 加载二进制数据到内存中, 生成对应的Class数据结构,
  2. 连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
  3. 初始化: 给类的静态变量赋初值

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

1
2
3
4
5
6
7
8
9

// 饿汉模式
class HungrySingleton{
private static HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){ }
public static HungrySingleton getInstance() {
return instance;
}
}

3.静态内部类

  1. 本质上是利用类的加载机制来保证线程安全
  2. 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一 种形式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class InnerClassSingleton{ 
    private static class InnerClassHolder{
    private static InnerClassSingleton instance= new InnerClassSingleton();
    }
    private InnerClassSingleton(){ }
    public static InnerClassSingleton getInstance(){
    return InnerClassHolder.instance;
    }
    }

评论