懒汉式单例模式由浅到深

1.最基础版,不考虑线程安全问题

package JavaBasis.chapter8;

public class Singleton {
    
    
    private static Singleton instance=null;
    private Singleton(){
    
    }//私有化构造函数,外部不可见
    private static Singleton getInstance()
    {
    
    
           if(instance==null)//实例未被创建
           instance=new Singleton();

           return instance;//返回创建的实例
    }
}

2.引入多线程产生的问题

上述代码在多线程环境下存在线程不安全问题,比如线程1先执行到了if判断语句这里,发现实例未被创建,于是开始创建实例;这时线程2又来了,但是线程1的instance还没有创建完,此时if判断依然满足,然后线程2也就进入了创建语句,导致最后会产生两个instace对象

  • 解决方案1 给整个方法加上一个锁
package JavaBasis.chapter8;

public class Singleton {
    
    
    private static Singleton instance=null;
    private Singleton(){
    
    }//私有化构造函数,外部不可见
    private static synchronized Singleton getInstance()
    {
    
    
           if(instance==null)//实例未被创建
           instance=new Singleton();

           return instance;//返回创建的实例
    }
}

这种解决方案的却可以解决线程安全问题,但是存在另外一个问题,由于锁住的是整个方法,假设我现在有一个线程1拿到了锁然后进入方法中创建好了对象,线程1结束,之后线程2来了进入方法发现对象已经被创建,直接返回,但是如果这时线程3也来了,由于锁住的是整个方法,线程3进不去,它必须等线程2结束才能进去,但是它只是想返回一个已经创建好的对象,浪费了时间,带来了性能开销,然后你可能会想出下面的解决方案

  • 解决方案2 给创建过程加上synchronized
package JavaBasis.chapter8;

public class Singleton {
    
    
    private static Singleton instance=null;
    private Singleton(){
    
    }//私有化构造函数,外部不可见
    private  static  Singleton getInstance()
    {
    
           if(instance==null)//实例未被创建
           {
    
    
           synchronized(Singleton.class)
           {
    
     instance=new Singleton();
           }
           }

           return instance;//返回创建的实例
    }
}

上述代码也存在线程安全问题,现在线程1先进入了方法并且拿到了锁开始创建对象,在线程1对象还没有创建完之前,线程2也进来了,线程2判断if发现对象未被创建,于是进入,但是拿不到锁,于是开始等待,等到线程1创建完对象释放锁之后线程2又去创建对象了,原因是它之前已经进行了if判断

  • 解决方案3 双重检验锁
package JavaBasis.chapter8;

public class Singleton {
    
    
    private static Singleton instance=null;
    private Singleton(){
    
    }//私有化构造函数,外部不可见
    private  static  Singleton getInstance()
    {
    
           if(instance==null)//实例未被创建
           {
    
    
           synchronized(Singleton.class)
           {
    
     
           if(instance==null)
           instance=new Singleton();
           }
           }

           return instance;//返回创建的实例
    }
}

在方案2存在的问题上再加上一个判断就可以解决上述问题,比如方案2中讲到的线程2,虽然它通过了第一个if判断,当是当它拿到锁以后再进行if判断时发现对象已经被线程1创建过了,因此直接返回对象,到此为止是不是觉得已经大功告成了,但是即使是双重检验锁,也存在一定问题

      if(instance==null)//实例未被创建
           {
    
    
           synchronized(Singleton.class)
           {
    
     
           if(instance==null)
           instance=new Singleton();//1
           }
           }

来看这段代码,1处创建对象的过程分为以下3步:
1.分配对象的内存空间
2.初始化对象
3. 让instance引用指向初始化对象的地址
但是再Java中会发生指令重排现像,具体来说就是2 3的顺序会发生改变,可能先执行3再执行2,这会导致什么问题
举个例子:线程1进入到了同步代码块开始创建对象,先执行了1,然后执行3,执行完了3instance不是nul了,instance指向一个明确的地址,只不过这个地址里面的对象没有初始化,在线程1还没有来得及执行2操作时,线程2进来了,它发现第一个if语句(也可能是第二个if语句)里面的instance不是null了,于是直接return instance了,然而instance是一个还未初始化的对象
解决方案:在instance前面加上volitile关键字,指令重排会关闭

猜你喜欢

转载自blog.csdn.net/qq_43478694/article/details/114966158
今日推荐