单例模式
单线程下的单例模式代码(懒汉,实用于单线程)
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实例。