单机模式下的单例
public class SingletonDemo {
private static SingletonDemo instace = null;
private SingletonDemo(){
System.out.println("enter the main method ..." );
}
public static SingletonDemo getInstace(){
if(instace == null){
instace = new SingletonDemo();
}
return instace;
}
public static void main(String[] args) {
System.out.println( SingletonDemo.getInstace() == SingletonDemo.getInstace());
System.out.println( SingletonDemo.getInstace() == SingletonDemo.getInstace());
System.out.println( SingletonDemo.getInstace() == SingletonDemo.getInstace());
}
}
// main方法只进入一次,说明只new了一次对象。getInstance多次都是true 验证了单例
上述的代码在单机模式下是没有问题的,但如果此时有多个线程同时去getInstance方法,就会出问题,如下:
public static void main(String[] args) {
/* System.out.println( SingletonDemo.getInstace() == SingletonDemo.getInstace());
System.out.println( SingletonDemo.getInstace() == SingletonDemo.getInstace());
System.out.println( SingletonDemo.getInstace() == SingletonDemo.getInstace());*/
for (int i = 0; i <= 10; i++) {
new Thread( ()->{
SingletonDemo.getInstace();
},String.valueOf( i ) ).start();
}
}
}
// main方法进入了多次,说明单例模式创建失败,因此在多线程下,这种方式来创建单例是不可取的
//解决方案:可以给getInstance方法加synchronized关键字,但synchronized级别太重,会导致性能较差,
多线程下,如何创建高效且安全的单例模式呢?
双端检锁机制 DCL(Double Check Lock)
在加锁前后加锁后都进行为null判断。
public class SingletonDemo {
private static SingletonDemo instace = null;
private SingletonDemo(){
System.out.println("enter the main method ..." );
}
public static SingletonDemo getInstace(){
if(instace == null){
synchronized(SingletonDemo.class){
if (instace == null){
instace = new SingletonDemo();
}
}
}
return instace;
}
public static void main(String[] args) {
for (int i = 0; i <= 10; i++) {
new Thread( ()->{
SingletonDemo.getInstace();
},String.valueOf( i ) ).start();
}
}
}
// 打印:enter the main method ...
// 说明:main方法进入了一次,说明单例模式创建成功
说明:DCL 不一定线程安全,因为会存在指令重排,假如volatile可以禁止指令重排。
原因在于:某个线程执行第一次检测,读到instance不为null,可能是已经分配空间,但未完成初始化。
instance = new SingltonDemo()可分为三步:
- memory = allocate()//1.分配内存
- instance(memory) //2.初始化对象
- instance = memory;//3. 设置instance指向分配的内存,此时 instance!=null
2,3 没有数据依赖关系,因此会发生指令重排
正确的解决方案:volatile+ DCL