设计模式—单例模式

133 阅读6分钟

设计模式—单例模式

单例模式—四种实现方式

1.单例设计模式保证一个类只有一个实例
2.要提供一个访问该类对象实例的全局访问点
3.属于创建者模式
4.隐藏其所有的构造方法 
5.提供一个获取bean对象的方法

第一种:饿汉式(静态常量)(√)

public class HungerSingleton {

    /**
     * 声明对象同时私有化
     */
    private static HungerSingleton instance = new HungerSingleton();

    /**
     * 构造函数私有化
     */
    private HungerSingleton() {
    }
    /**
     * 向外声明访问该类对象的方法
     * @return HungerSingleton的对象
     */
    public static HungerSingleton getInstance() {
        return instance;
    }
}

特点 通过静态变量的方式初始化一个bean实例,一旦类加载就创建了一个单例,此时还没有使用这个bean的线程,所以这种方式一定是线程安全的。

优点

  1. 线程安全:由于在类加载时就创建了单例对象,因此不需要考虑多线程访问的问题。这种方式是线程安全的。
  2. 效率高:由于在类加载时就创建了单例对象,因此在访问单例对象时不需要进行线程同步和锁定操作,因此效率比较高。

缺点

  1. 占用内存:由于在类加载时就创建了单例对象,因此会占用一定的内存空间。如果单例对象比较大,或者有很多单例对象,会占用较多的内存空间。
  2. 可能造成资源浪费:由于在类加载时就创建了单例对象,因此如果这个单例对象很长时间都没有被使用,会造成资源的浪费。
  3. 不支持延迟加载:由于在类加载时就创建了单例对象,因此无法支持延迟加载。如果单例对象的创建比较耗时,或者需要进行一些初始化操作,会影响程序的启动时间。

使用场景 饿汉式单例模式适用于单例对象较小、创建比较简单、使用频率比较高的情况

第二种:懒汉式

饿汉式可能会造成内存浪费的情况,为了解决这种情况可以使用懒汉式。类加载时初始化一个空的实例变量,在具体线程调用获取的实例的方法时在创建单例对象。

懒汉式——基础版本(非线程安全)

public class LazySingleton {
 //首先声明一个空的对象,不是实例化
 private static LazySingleton instance = null;
 //构造函数私有化
 private LazySingleton() {}
 //提供获取实例的方法
 public static LazySingleton getInstance() {
     if (instance == null) {
         instance = new LazySingleton();
     }
     return instance;
 }
}

优点

  1. 支持延迟加载:由于在第一次使用时才创建单例对象,因此可以支持延迟加载。如果单例对象的创建比较耗时,或者需要进行一些初始化操作,可以在第一次使用时再进行创建,从而提高程序的启动时间。
  2. 节约资源:由于在第一次使用时才创建单例对象,因此可以避免在程序启动时就占用过多的内存空间。如果单例对象比较大,或者有很多单例对象,可以节约一定的资源。

缺点

  1. 线程安全问题:由于在第一次使用时才创建单例对象,因此需要考虑多线程访问的问题。如果多个线程同时访问单例对象,可能会创建多个实例,从而导致单例失效

使用场景 单线程情况下使用

懒汉式——线程安全,同步方法

针对上一个版本的非线程安全版本进行改进,为了保证器线程安全,对其获取实例方法进行加锁,保证在多线程的情况下只有一个线程可以执行放进行获取的bean方法。这样第一个执行方法线程进行了bean的实例化,后续的线程便直接获取bean的实例。

public class LazySingleton {
    //首先声明一个空的对象,不是实例化
    private static LazySingleton instance = null;
    //构造函数私有化
    private LazySingleton() {}
    //提供获取实例的方法
    public static synchronized  LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

存在的缺陷

效率低:由于需要进行线程同步机制来保证线程安全,因此在访问单例对象时需要进行锁定操作,会影响程序的性能,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码,后面的线程获得该类实例,直接return就行了。方法进行同步效率太低要改进。

懒汉式——线程不安全,同步代码块

public class LazySingleton {
    //首先声明一个空的对象,不是实例化
    private static LazySingleton instance = null;
    //构造函数私有化
    private LazySingleton() {}
    //提供获取实例的方法
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class){
                instance = new LazySingleton();
            }
        }
        return instance;
    }
}

存在的缺陷

这种同最开的 [基础版本] 一样,在多线程的情况下无法保证单例,当一个线程进入了if (singleton == null)判断语句块,另一个线程也进入判断的情况下,两个线程就会依次创建两个实例,从而导致单例失效。

懒汉式——双重检查【推荐使用】

public class LazySingleton {
    //首先声明一个空的对象,不是实例化
    private static LazySingleton instance = null;

    //构造函数私有化
    private LazySingleton() {
    }

    //提供获取实例的方法
    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

优点

线程安全;延迟加载;效率较高。所以推荐使用。

第三种:静态内部类【推荐使用】

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这种方式有点像饿汉式结合,通过和饿汉式一样采用了类装载的机制来保证初始化实例时只有一个线程,保证单例多线安全。不同的地方,饿汉式只要Singleton类被装载就会实例化,没有Lazy-Loading的作用。而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,完成Singleton的实例化。而INSTANCE为类的静态属性,只会在第一次加载类的时候初始化,JVM保证了线程的安全性, 因为类进行初始化时,别的线程是无法进入的。所有这样Singleton类只会被实例化一次。

第四种:枚举【推荐使用】

public enum Singleton {
    INSTANCE;

    // 单例类的其他方法
    public void doSomething() {
        // ...
    }
}

使用枚举实现单例模式是最简单、最安全的方式之一,因为枚举类型的实例只能在枚举类型内部创建,且在任何情况下都是单例。 Singleton是一个枚举类型,其中只有一个枚举常量INSTANCE,它就是单例的实例。在使用时,可以直接通过Singleton.INSTANCE来获取单例的实例,并调用其方法。

优点

  1. 线程安全,不需要考虑多线程同步问题;
  2. 能够防止反序列化重新创建新的实例;
  3. 简洁明了,代码量少。