单例模式

单例模式

  • 当系统中对于某个类,只需要有一个实例化对象时,可以使用单例模式。

单例模式实现方式

单例模式的实现主要有两个特征

  1. 私有化构造方法,让外部不能直接通过构造方法创建对象实例;
  2. 提供一个静态的公共方法( 例如下方的getInstance() ),外部通过这个方法获取对象实例。

在系统启动时即创建--俗称饿汉式

public class Singleton {

    private Singleton() {

    }
    private static Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        
        return singleton;
    }
}

第一次调用时才创建--俗称懒汉式

public class Singleton {

    private Singleton() {

    }
    private static Singleton singleton;

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

懒汉式单例模式存在的问题

在并发编程的场景下,如果多个线程同时调用getInstance()方法,可能会存在创建多个对象实例的情况,为了避免这种错误的问题产生,可以使用锁来解决问题。

1. 给方法加锁
public class Singleton {

    private Singleton() {

    }
    private static Singleton singleton;

    public synchronized static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
  • 这种方式开销过大,每个线程进入方法前都要获得锁;如果实例已经被创建,访问时直接返回即可,不存在线程安全问题
2. 锁代码块
public class Singleton {

    private Singleton() {

    }
    private static Singleton singleton;

    public  static Singleton getInstance() {
        if (singleton == null) {            
            synchronized (Singleton.class) {
                
                singleton = new Singleton();
            }
            
        }
        return singleton;
    }
}
  • 这种方式的好处是:当实例已经创建时,线程访问时就不需要再获取锁,可以直接返回。实例不存在时,保证只有单个线程在创建实例

但是这种加锁方式依然存在问题,假如有两个线程A,B调用getInstance()方法,在线程A还没有实例化完成时,线程B也可以通过if判断进入内部,等待锁释放并创建对象,此时对象依然创建了多个。


temp.png

为了解决这种问题,于是提出了以下方式。

3. DCL(Double Check Lock)双重检查锁
public class Singleton {

    private Singleton() {

    }
    private static Singleton singleton;

    public  static Singleton getInstance() {
        if (singleton == null) {            
            synchronized (Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton();
                }
                
            }
            
        }
        return singleton;
    }
}
  • 获取锁后,在进行一次为空判断,这样就可以解决上面提出的问题

DCL的方式看起来似乎完美无缺,但遗憾的是,其中依然存在问题。在说明问题前,先了解下什么是“指令重排序”

  • 指令重排序
    JVM在执行代码时,对没有数据依赖的操作步骤,可能会以指令重排序的方式执行代码,提高执行效率。如下图:


    cpx.png

所以DCL模式也会出现以下问题


dcl.png

因此,线程可能会获取到一个未初始化完成的对象


dcl1.png
4. 最终写法
  • 使用volatile修饰singleton

volatile可以禁止指令重排序,保证内存可见性。具体实现原理参考《java并发编程的艺术》一书。限于篇幅,不再说明。

public class Singleton {

    private Singleton() {

    }
    private volatile static Singleton singleton;

    public  static Singleton getInstance() {
        if (singleton == null) {            
            synchronized (Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton();
                }
                
            }
            
        }
        return singleton;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。