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