设计模式 五种不同的单例模式 懒汉式 饿汉式 枚举单例 容器化单例(Spring单例源码分析) 线程单例

单例模式

第一种 饿汉式

优点:执行效率高,性能高,没有任何的锁

缺点:某些情况下,可能会造成内存浪费

java 复制代码
/**
 * @author LionLi
 */
public class HungrySingleton {

    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){}

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

测试用例与结果

java 复制代码
/**
 * @author LionLi
 */
public class Test {
    public static void main(String[] args){
        ExecutorService executor = Executors.newFixedThreadPool(5);
        PrintStream out = System.out;
        executor.execute(() -> out.println(HungrySingleton.getInstance()));
        executor.execute(() -> out.println(HungrySingleton.getInstance()));
        executor.execute(() -> out.println(HungrySingleton.getInstance()));
        executor.execute(() -> out.println(HungrySingleton.getInstance()));
        executor.execute(() -> out.println(HungrySingleton.getInstance()));
        executor.shutdown();
    }
}


多次运行符合要求不会出现问题

第二种 懒汉式

优点:节省了内存,线程安全

缺点:性能低

测试用例以下通用

java 复制代码
/**
 * @author LionLi
 */
public class Test {
    public static void main(String[] args){
        ExecutorService executor = Executors.newFixedThreadPool(5);
        PrintStream out = System.out;
        executor.execute(() -> out.println(LazySingletion.getInstance()));
        executor.execute(() -> out.println(LazySingletion.getInstance()));
        executor.execute(() -> out.println(LazySingletion.getInstance()));
        executor.execute(() -> out.println(LazySingletion.getInstance()));
        executor.execute(() -> out.println(LazySingletion.getInstance()));
        executor.shutdown();
    }
}

第一版本

java 复制代码
/**
 * @author LionLi
 */
public class LazySingletion {
    private static LazySingletion instance;
    private LazySingletion(){}

    public static LazySingletion getInstance(){
        if(instance == null){
            instance = new LazySingletion();
        }
        return instance;
    }
}

测试失败无法保证单例

第二版本 增加 synchronized 锁

java 复制代码
/**
 * @author LionLi
 */
public class LazySingletion {
    private static LazySingletion instance;
    private LazySingletion(){}

    public synchronized static LazySingletion getInstance(){
        if(instance == null){
            instance = new LazySingletion();
        }
        return instance;
    }
}


测试成功 可以保证单例 但性能较低 所有的线程全都被阻塞到方法外部排队处理

第三版本 细化锁 只锁创建方法提高性能

优点: 性能高了,线程安全了

缺点:可读性难度加大,不够优雅

此方法在各大开源框架源码内最为常见 又名双重校验单例

java 复制代码
/**
 * @author LionLi
 */
public class LazySingleton {
    /**
     * volatile 保证原子性具体用法百度
     */
    private volatile static LazySingleton instance;
    private LazySingleton(){}

    public static LazySingleton getInstance(){
        // 检查实例是否已经初始化 如果已经初始化直接返回 避免进入锁提高性能
        if (instance == null) {
            synchronized (LazySingleton.class) {
                // 重新检查是否已经被其他线程初始化
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}


测试成功 可以保证单例 性能还高 可以避免不必要的加锁

第三种 枚举单例

在这种实现方式中,既可以避免多线程同步问题,还可以防止通过反射和反序列化来重新创建新的对象。
Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。

java 复制代码
/**
 * @author LionLi
 */
public enum EnumSingleton {
    INSTANCE;
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
}

测试代码与结果

java 复制代码
/**
 * @author LionLi
 */
public class Test {
    public static void main(String[] args){
        ExecutorService executor = Executors.newFixedThreadPool(5);
        PrintStream out = System.out;
        // 设置一个对象便于查看内存地址
        EnumSingleton.INSTANCE.setData(new Object());
        executor.execute(() -> out.println(EnumSingleton.INSTANCE.getData()));
        executor.execute(() -> out.println(EnumSingleton.INSTANCE.getData()));
        executor.execute(() -> out.println(EnumSingleton.INSTANCE.getData()));
        executor.execute(() -> out.println(EnumSingleton.INSTANCE.getData()));
        executor.execute(() -> out.println(EnumSingleton.INSTANCE.getData()));
        executor.shutdown();
    }
}


测试成功 可以保证单例 代码简单非常优雅

第四种 Spring中的单例模式实现 也可以称为 容器化单例

大家可以通过idea搜索找到 Spring 源码中的 DefaultSingletonBeanRegistrygetSingleton 方法 查看 Spring 是如何编写的

这里涉及到三个单例容器:

  • singletonObjects
  • earlySingletonObjects
  • singletonFactories

单例的获取顺序是singletonObjects -> earlySingletonObjects -> singletonFactories 这样的三级缓存
我们发现,在 singletonObjects 中获取 bean的时候,没有使用 synchronized 关键字

而在 singletonFactoriesearlySingletonObjects 中的操作都是在 synchronized 代码块中完成的,正好和他们各自的数据类型对应

singletonObjects 使用的使用 ConcurrentHashMap 线程安全,而 singletonFactoriesearlySingletonObjects 使用的是 HashMap 线程不安全。
从字面意思来说:singletonObjects 指单例对象的缓存,singletonFactories 指单例对象工厂的缓存,earlySingletonObjects 指提前曝光的单例对象的缓存。

以上三个构成了三级缓存,Spring 就用这三级缓存巧妙的解决了循环依赖问题。
除了这三个缓存之外 最核心的就是上面讲到的 双重校验单例 写法

第五种 特殊单例 线程单例

顾名思义 保证在所有线程内的单例

常见使用场景 日志框架 确保每个线程内都有一个单例日志实例 保证日志记录和输出的唯一性
提到线程 我们肯定会想到 在线程内最常使用的东西 那就是 TheadLocal 他可以保证线程之间的变量隔离 我们就基于他来实现线程单例

java 复制代码
public class ThreadLocalSingleton {
    // 通过 ThreadLocal 的初始化方法 withInitial 初始化对象实例 保证线程唯一
    private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
            ThreadLocal.withInitial(() -> new ThreadLocalSingleton());

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocaLInstance.get();
    }
}

测试用例与运行结果

java 复制代码
/**
 * @author LionLi
 */
public class Test {
    public static void main(String[] args){
        ExecutorService executor = Executors.newFixedThreadPool(5);
        PrintStream out = System.out;
        executor.execute(() -> {
            out.println(ThreadLocalSingleton.getInstance());
            out.println(ThreadLocalSingleton.getInstance());
        });
        executor.execute(() -> {
            out.println(ThreadLocalSingleton.getInstance());
            out.println(ThreadLocalSingleton.getInstance());
        });
        executor.shutdown();
    }
}

测试符合预期 不同线程下的实例是单例的

相关推荐
哪 吒6 分钟前
最简单的设计模式,抽象工厂模式,是否属于过度设计?
设计模式·抽象工厂模式
Theodore_10227 分钟前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
转世成为计算机大神3 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
小乖兽技术4 小时前
23种设计模式速记法
设计模式
小白不太白9506 小时前
设计模式之 外观模式
microsoft·设计模式·外观模式
小白不太白9506 小时前
设计模式之 原型模式
设计模式·原型模式
澄澈i6 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
小白不太白95013 小时前
设计模式之建造者模式
java·设计模式·建造者模式
孟秋与你15 小时前
【spring】spring单例模式与锁对象作用域的分析
java·spring·单例模式
菜菜-plus15 小时前
java 设计模式 模板方法模式
java·设计模式·模板方法模式