设计模式(1)——单例模式

目录

1. 单例模式的含义

2. 单例模式的四种常见方式

2.1 饿汉式经典加载方式

2.2 懒汉式加载方式

2.3 静态内部类方式加载

2.4 枚举类加载方式


1. 单例模式的含义

单例,简单来说就是单个实例,是一种常见的软件的设计模式。

一个类只实例化自己的一个实例,并且不允许再创建多余的实例,在对外提供访问时,访问的都是同一个实例对象。

2. 单例模式的四种常见方式

2.1 饿汉式经典加载方式

很好理解,饿的见到什么都吃,在单例模式中指不管用到用不到的资源,都统统加载到内存中。

优点:实现简单,实用性强,且不存在线程安全问题,是我们最常使用的一种实现方式;

缺点:不管用到与否,类加载时就完成实例化统统加载到内存,用不用都要加载,感觉有点浪费和多余,占用内存空间;

代码实现如下,这种方法实现单例模式的核心原理是JVM会且仅会将一个类加载一次到内存中去,加载一次 Mgr01 类,那么 INSTANCE 对象就被创建出来了

public class Mgr01 {
# 创建一个静态私有化 INSTANCE 对象
    private static final Mgr01 INSTANCE = new Mgr01();
# 私有化构造器,不允许其他人再创建新的类对象
    private Mgr01(){}
# 编写get方法,调用get方法即可返回已经创建好的 INSTANCE 对象
    public static Mgr01 getInstance(){
        return INSTANCE;
    }
# 此处main方法为测试方法,与设计模式无关
    public static void main(String[] args) {
        Mgr01 m1 = new Mgr01().getInstance();
        Mgr01 m2 = new Mgr01().getInstance();
        System.out.println(m1 == m2);
    }
}

我们来运行一下 main 方法,就会发现 m1 对象和 m2 对象是相等的,如下图所示

2.2 懒汉式加载方式

对比饿汉式加载,懒汉式最大的特点就是懒,我不加载,我在需要用到的时候才去进行加载。

public class Mgr02 {
// 创建一个静态 Mgr02 对象 INSTANCE,但不进行实例化,
// 并且不许使用 volatile 修饰禁止指令重排,否则可能会出错,设计知识点较深,暂不解说
    public static volatile Mgr02 INSTANCE;

// 私有化无参构造,禁止其他人访问创建新实例
    private Mgr02(){}

// 在 get 方法中进行对象的实例化,并采用双重校验实现线程安全
    public static Mgr02 getInstance(){
        // 第一次检查,如果 INSTANCE 不为空,直接返回 INSTANCE 对象
        if (INSTANCE == null){
            // 双重检查再次判断避免出现线程安全问题
            synchronized (Mgr02.class){
                if (INSTANCE == null){
                    INSTANCE = new Mgr02();
                }
            }
        }
        return INSTANCE;
    }
// 此处为测试 main方法测试线程安全
    public static void main(String[] args) {
// 循环一百次,创建一百个线程
        for (int i = 0; i < 100; i++) {
// 匿名内部类的方法创建线程对象
            new Thread(new Runnable() {
                @Override
                public void run() {
// 打印获取到的实例对象的哈希码,如果一样则说明为同一个对象
                    System.out.println(Mgr02.getInstance().hashCode());
                }
            }).start;
        }
    }
}

运行 main 方法,我们可以在控制台发现打印的对象的哈希码是一样的,说明这100个线程对象获取到的 INSTANCE 对象本质上都是同一个;

2.3 静态内部类方式加载

静态内部类是通过JVM的方式保证线程安全,JVM会保证每个类之加载一次,但类中的静态内部类是不会被加载的,只有当我们调用 get 方法时,get 方法会返回给我们一个静态内部类的INSTANCE,而INSTANCE在静态内部类中指代的就是外部类的实例对象,非常神奇,非常花哨,并且可以做到线程安全,任何人来访问get方法获取Mgr03对象的实例,获取到的都是同一个INSTANCE。

实现代码如下所示

public class Mgr03 {
// 私有化构造方法
    private Mgr03(){}
// 定义静态内部类,静态内部类不会随着类的加载而加载
    private static class Mgr03Helper{
        private final static Mgr03 INSTANCE = new Mgr03();
    }
// 定义静态 get 方法
    public static Mgr03 getInstance(){
        return Mgr03Helper.INSTANCE;
    }
// 此处为 测试 main方法,测试是否有线程安全问题
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Mgr03.getInstance().hashCode());
                }
            }).start();
        }
    }
}

运行此方法,如下图所示,所有对向的哈希码都是一样的,

 

2.4 枚举类加载方式

枚举类实现单例模式的方法更为简单,我们只需要定义一个枚举类,然后在枚举类中定义一个INSTANCE对象,枚举类会在变量的前面默认添加 "public static final",所以不需要我们再额外添加

// 定义一个枚举类 Mgr01
public enum Mgr04 {
// 定义 INSTANCE 对象
    INSTANCE;
// 此处为 main 测试方法,测试是否线程安全
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Mgr04.INSTANCE.hashCode());
                }
            }).start();
        }
    }
}

运行 main 方法,可以在控制台发现所有的对象的哈希码都是一样的,是线程安全的

上一篇:Kubernetes(K8S)学习(三):K8S实战案例-二、部署SpringBoot项目


下一篇:HarmonyOS 应用开发之创建PageAbility