设计模式[1]-单例模式

代码:https://gitee.com/Aes_yt/design-pattern

单例模式

单例模式(Singleton)是一种创建型设计模式,能够保证一个类只有一个实例,并提供了访问该实例的全局节点。

单例的实现步骤有以下步骤,首先将默认构造函数设置为私有,然后创建一个静态方法来调用私有构造函数来创建对象。

单例类型主要分为懒汉模式和饿汉模式。

  • 饿汉:类加载就会导致单例对象被创建。
  • 懒汉:类加载不会导致单例对象被创建,首次使用该对象时才会创建。

1. 饿汉模式

1.1 静态变量生成
public class HungrySingleton1 {
    // 1. 类中直接创建本类实例
    private static HungrySingleton1 singleton = new HungrySingleton1();

    // 2. 私有构造方法
    private HungrySingleton1() {
    }

    // 3. 公共静态方法,供外部访问
    public static HungrySingleton1 getInstance() {
        return singleton;
    }
}

单元测试通过:

    @Test
    void getInstance() {
        HungrySingleton1 instance1 = HungrySingleton1.getInstance();
        HungrySingleton1 instance2 = HungrySingleton1.getInstance();
        Assertions.assertEquals(instance1, instance2);
    }
1.2 静态代码块生成
public class HungrySingleton2 {
    // 1. 声明静态私有变量,在静态代码块中创建本类实例
    private static HungrySingleton2 singleton;

    static {
        singleton = new HungrySingleton2();
    }

    // 2. 私有构造方法
    private HungrySingleton2() {
    }

    // 3. 公共静态方法,供外部访问
    public static HungrySingleton2 getInstance() {
        return singleton;
    }
}

单元测试通过:

    @Test
    void getInstance() {
        HungrySingleton2 instance1 = HungrySingleton2.getInstance();
        HungrySingleton2 instance2 = HungrySingleton2.getInstance();
        Assertions.assertEquals(instance1, instance2);
    }

饿汉模式是类加载就会创建对象,所以会造成内存浪费。优点是代码简单,而且不用考虑多线程问题。

2. 懒汉模式

2.1 线程不安全
public class LazySingleton1 {
    // 1. 声明静态私有变量
    private static LazySingleton1 singleton;

    // 2. 私有构造方法
    private LazySingleton1() {
    }

    // 3. 公共静态方法,供外部访问,在此处创建对象
    public static LazySingleton1 getInstance() {
        if (singleton == null) {
            singleton = new LazySingleton1();
        }
        return singleton;
    }
}

在getInstance方法中,先判断变量是否存在,不存在的话,就创建对象并返回。但是这种方法是线程不安全的,所以为了保证线程安全,可以给getInstance方法加锁,即2.2的创建方法。

2.2 线程安全,方法加锁
public class LazySingleton2 {
    // 1. 声明静态私有变量
    private static LazySingleton2 singleton;

    // 2. 私有构造方法
    private LazySingleton2() {
    }

    // 3. 公共静态方法,供外部访问,在此处创建对象
    public static synchronized LazySingleton2 getInstance() {
        if (singleton == null) {
            singleton = new LazySingleton2();
        }
        return singleton;
    }
}

跟2.1的代码,主要只有一行有所区别。

public static synchronized LazySingleton2 getInstance()

2.3 线程安全,双重检查锁校验
public class LazySingleton3 {
    // 1. 声明静态私有变量
    private static volatile LazySingleton3 singleton;

    // 2. 私有构造方法
    private LazySingleton3() {
    }

    // 3. 公共静态方法,供外部访问,在此处创建对象
    public static LazySingleton3 getInstance() {
        // 第一次判断,实例不为空,直接返回
        if (singleton == null) {
            synchronized (LazySingleton3.class){
                // 第二次判断
                if(singleton == null){
                    singleton = new LazySingleton3();
                }
            }
        }
        return singleton;
    }
}

2.2的创建方式中,锁直接加在getInstance方法,每次调用此方法都会加锁,过于笨重。

所以改进方式是,利用两次判断,第一次判断,如果实例存在,就返回,那如果不存在,就是得加锁,而第二次判断保证了创建的是单一实例。

仔细观察2.3的代码思路,其实是2.1和2.2的结合。

注意:

singleton 变量使用volatile关键字修饰,是因为多线程情况下,有可能出现空指针异常,因为jvm实例化对象的时候会进行指令优化和指令重排序,加上volatile关键字可以保证可见性和有序性。

2.4 静态内部类实现
public class LazySingleton4 {
    // 1. 私有构造方法
    private LazySingleton4() {
    }

    // 2. 声明静态内部类
    private static class InnerSingleton {
        // 3. 内部类中创建外部类的常量对象
        private static final LazySingleton4 SINGLETON = new LazySingleton4();
    }

    // 4. 公共静态方法,供外部访问,返回静态内部类中创建的常量对象
    public static LazySingleton4 getInstance() {
        return InnerSingleton.SINGLETON;
    }
}

JVM加载外部类的时候,不会加载静态内部类,只有在调用内部类的属性或方法的时候才会加载,而且静态内部类中的属性用static修饰,保证了单例,所以这种方法是线程安全且没有造成性能影响和空间的浪费。

posted @ 2024-08-23 11:05  Aeons  阅读(3)  评论(0编辑  收藏  举报