单例模式

定义

确保某一个类只有一个实例,并且自行实例化向整个系统提供这个实例

  • 构造函数不对外开放,一般为private
  • 通过一个静态方法或者枚举返回单例对象
  • 确保单例类的对象有且只有一个,尤其是多线程的环境下
  • 确保单例类对象在反序列化时不会重新构建对象

实现单例的方式

  1. 饿汉模式
  • 急切初始化,没有懒加载
public class Singleton {
    private static final Singleton instance= new Singleton();
    
    private Singleton() {
        
    }
    
    public static Singleton getInstance() {
        return instance;
    }
}
  1. 懒汉模式
  • synchronized 关键字保证了多线程情况下单例对象的唯一性
  • 问题是每次获取实例都需要进行同步,造成资源的浪费。
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
        
    }
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        
        return instance;
    }
}
  1. DoubleCheckLock(DCL)实现单例
  • 资源利用率高,第一次执行 getInstance 的时候单例对象才会实例化,效率高。
  • 第一次加载反应稍慢;由于Java内存模型的原因,在高并发的环境下,会发生概率很小的失效问题
public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {
        
    }
    public static Singleton getInstance() {
        if(instance == null) {
            synchrozed(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instace;
    }
}

这种方式亮点在于做了两次判空检查:第一次判断是避免不必要的同步操作,第二次则保证实例对象的唯一性和准确性。怎么理解第二次判空呢?
如果两个线程 A B 。他们都进入到了第一次判空,这个时候线程A获取到了锁,如果没有第二次判空,会进行对象的实例化,执行完成后释放锁,这个时候切换到线程B,同样会进行对象的实例化,这样对象的唯一性就出现了问题。那么,DCL 方式有什么问题么?目前上面的写法是存在问题的,我们分析下:

instance = new Singleton();

并不是一个原子操作。它可以分为下面三个步骤:

  1. 给Singleton 的实例分配内存;
  2. 调用 Singleton 的构造函数,初始化成员字段;
  3. 将 instance 对象指向分配的内存空间。
    但是由于Java编译器允许处理器乱序执行以及 JDK 1.5 之前的 Java 内存模型中 Cache、寄存器到主内存回写顺序的规定,2 和 3 的顺序无法保证。即执行顺序可能是1-2-3,也可能是1-3-2.如果是后者,在线程 A 已经执行完3的时候切换到了线程B,线程B的判断就是 instance 非空,所以线程B会直接使用该实例对象,这个时候就会出错。这就是DCL失效问题。我们怎么避免这个问题呢?在JDK1.5之后,我们可以使用 valatile 关键字:
private volatile static Singleton instance = null;

这样我们可以保证 instance 每次都是从主内存中读取。
4. 静态内部类方式

public class Singleton {
    private Singleton(){
        
    }
    
    public static getInstance() {
        return SingletonHolder.sInstance;
    }
    
    private static classs SingletonHolder {
        private static final Singleton sInstance = new Singleton();
    }
}

第一次加载 Singleton 类时并不会初始化 sInstance,只有在第一次调用 Singleton 的 getInstance 方法时才会导致 sInstance 被初始化。这种方式不仅能保证线程安全,也能够保证单例对象唯一性,同时也延迟了单例的实例化,所以推荐这种使用方式。

你可能感兴趣的:(设计模式,设计模式,单例模式,单例)