单例模式

单线程下的单例模式代码(懒汉,实用于单线程)

public class SingletonDemo {    //用动态变量保留这个惟一实例    private static SingletonDemo instance = null;        //结构器私有化    private SingletonDemo() {    }        //提供一个静态方法来获取实例对象    public static SingletonDemo getInstance() {        if (instance == null) {            instance = new SingletonDemo();        }        return instance;    }}

单线程下创立进去的都是同一个对象。然而在多线程的环境下,咱们的单例模式是否还是同一个对象了?

public class SingletonDemo {    //用动态变量保留这个惟一实例    private static SingletonDemo instance = null;    //结构器私有化    private SingletonDemo() {        System.out.println(Thread.currentThread().getName() + "\t 我是构造方法");    }    //提供一个静态方法来获取实例对象    public static SingletonDemo getInstance() {        if (instance == null) {            instance = new SingletonDemo();        }        return instance;    }    //测试多线程是否是同一个实例!    public static void main(String[] args) {        for (int i = 0; i < 10; i++) {            new Thread(() -> {                SingletonDemo.getInstance();            }, "线程" + i).start();        }    }}
  • 从上面的后果咱们能够看出,咱们通过SingletonDemo.getInstance() 获取到的对象,并不是同一个,而是被上面几个线程都进行了创立,那么在多线程环境下,单例模式如何保障呢?

解决办法1

引入synchronized关键字

public synchronized static SingletonDemo getInstance() {    if(instance == null) {        instance = new SingletonDemo();    }    return instance;}

然而synchronizaed属于重量级的同步机制,它只容许一个线程同时拜访获取实例的办法,然而因而减低了并发性,因而采纳的比拟少。

解决办法2

通过引入DCL Double Check Lock 双端检锁机制

就是在进来和进来的时候,进行检测

public static SingletonDemo getInstance() {    if (instance == null) {        synchronized (SingletonDemo.class) {            if (instance == null) {                instance = new SingletonDemo();            }        }    }    return instance;}

DCL的确可能保障单例模式的正确性,然而下面的办法还是存在问题的。

DCL(双端检锁)机制不肯定是线程平安的,起因是有指令重排的存在,退出volatile能够禁止指令重排

private static volatile SingletonDemo instance = null;

因为instance能够分为三局部进行实现:

  • memory = allocate() // 1、调配对象内存空间
  • instance(memory) // 2、初始化对象
  • instance = memory // 3、设置instance指向刚刚调配的内存地址,此时instance != null

失常来说执行完2语句再执行3语句,对象初始化后,咱们才指向内存地址。

然而执行完1语句后,步骤2 和 步骤3之间不存在 数据依赖关系,而且无论重排前 还是重排后,程序的执行后果在单线程中并没有扭转,因而这种重排优化是容许的。

这样就会造成什么问题呢?

咱们先执行步骤2时,因为对象的初始化还没有实现,所以试图获取instance时,会失去null。因而执行单例模式的代码时候,就会从新再创立一个instance实例。