单例模式的4种实现方式

简介: 单例模式的4种实现方式

1:前言

  • 单例模式:保证一个类只有一个实例,并且提供一个全局访问点
  • 使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池

2:懒汉模式

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

  1. 存在的问题:
  2. 线程安全问题
  3. double check 加锁优化
  4. 编译器 (JIT) , CPU 有可能对指令进行重排序,导致使用到尚未初始化 的实例,可以通过添加volatile 关键字进行修饰, 对于volatile 修饰的字段,可以防止指令重排。
public 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.1测试

public class LazySingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        });
        Thread t2 = new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        });
        t1.start();
        t2.start();
    }
}

打印结果如下:

org.hzz.singleton.v1.LazySingleton@4a10f8eb
org.hzz.singleton.v1.LazySingleton@4a10f8eb

3:饿汉模式

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

3.1 类加载过程:

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

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

public class HungrySingleton {
    private final static HungrySingleton instance = new HungrySingleton();
    public HungrySingleton(){
        System.out.println("HungrySingleton 实例化");
    }
    public static HungrySingleton getInstance(){
        return instance;
    }
}

3.2 测试

public class HungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton instance1 = HungrySingleton.getInstance();
        HungrySingleton instance2 = HungrySingleton.getInstance();
        System.out.println(instance2 == instance1);
    }
}
/**
 * true
 */

4:静态内部类

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

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

public class InnerClassSingleton {
    private static class InnerClassHolder{
        static {
            System.out.println("InnerClassHolder 初始化");
        }
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    //构造器私有化
    private InnerClassSingleton(){}
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

4.1 反射攻击

public class ReflectAtack {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Constructor<InnerClassSingleton> constructor =                            InnerClassSingleton.class.getDeclaredConstructor();
        //越过构造器私有化
        constructor.setAccessible(true);
        // 通过发射InnerClassHolder的static静态代码块没有执行
        InnerClassSingleton innerClassSingleton = constructor.newInstance();
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        System.out.println(innerClassSingleton == instance); // false
    }
}

4.2 静态内部类防止反射破坏

public class InnerClassSingleton {
    private static class InnerClassHolder{
        static {
            System.out.println("InnerClassHolder 初始化");
        }
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        //当通过反射攻击走到这一步
        if(InnerClassHolder.instance == null){  // 有意思
            throw new RuntimeException("单例不允许多个实例");  
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

5:枚举类型

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

利用类加载机制保证线程安全

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



目录
相关文章
|
2天前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
67 0
|
2天前
|
设计模式 缓存 安全
【设计模式】单例模式:确保类只有一个实例
【设计模式】单例模式:确保类只有一个实例
26 0
|
2天前
|
C++
C++实现单例模式-多种方式比较
单例模式,面试中经常被问到,但是很多人只会最简单的单例模型,可能连多线程都没考虑到,本文章从最简单的单例,到认为是最佳的单例模式实现方式,单例模式没有什么知识点,直接上源码
53 0
|
6月前
|
设计模式 存储 安全
八种创建单例模式的方式-懒汉式与饿汉式及枚举
八种创建单例模式的方式-懒汉式与饿汉式及枚举
85 2
|
7月前
|
设计模式 安全 Java
JAVA设计模式1:单例模式,确保每个类只能有一个实例
JAVA设计模式1:单例模式,确保每个类只能有一个实例
|
7月前
|
设计模式 安全 Java
特殊类设计及单例模式(C++)
特殊类设计及单例模式(C++)
66 1
|
8月前
单例模式设计(一)
饿汉模式 由名字我们就可以知道 &quot;饿汉&quot; 嘛,就比较急切,在类加载的时候就创建实例: 1. 写一个类,在本类中构造实例,用static修饰,直接创建出来(提供一个现有的实例) 2. 在本类中写一个方法获取到上面的实例 3. 将这个类的构造方法设置为私有的,让外部不能 new 这个对象
39 0
|
2天前
|
设计模式 安全 Java
Java设计模式—单例模式的实现方式和使用场景
那么为什么要有单例模式呢?这是因为有的对象的创建和销毁开销比较大,比如数据库的连接对象。所以我们就可以使用单例模式来对这些对象进行复用,从而避免频繁创建对象而造成大量的资源开销。
65 1
|
8月前
|
安全
单例模式设计(二)
根据上面的 "懒汉模式" 和 "饿汉模式"。我们可以知道,懒汉模式,它只是负责读取,没有修改。而 " 饿汉模式 " 是既读取,也进行修改。所以来说, "懒汉模式" 是线程安全的, "饿汉模式" 是线程不安全的。
38 0
|
9月前
|
设计模式 安全 编译器
单例模式实现的四种方法详解
单例模式实现的四种方法详解
59 0