单例模式双重锁中volatile的作用

对于学android开发的同学来说,单例模式应该在熟悉不过了吧,单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,还可以分为饿汉式和懒汉式,这篇文章浅谈下懒汉式,重点讲一下饿汉式的volatile的作用,如有什么问题,还请大家多多指教哈!!!

  • 懒汉式

顾名思义,就是一个很懒的人,他什么都不想动,就只想着躺平,只有到饿了的时候才会去找吃的,简单来说就是一开始并不会去实例化,只有等到要用上的时候才会去实例化~

public class Singleton {  
    private static Singleton instance;  

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

这种方式实现了要用的时候才实例化,虽然也使用了synchroized来保证线程的安全,但是效率相对来说会偏低。

  • 饿汉式

相对于懒汉式来说,这个人就很勤奋了,一大早就准备好了今天的所有食物,这样就不会饿着了,就是饿汉式在一开始类加载的时候就已经实例化,并且创建单例对象,随时都可以使用了;

public class Singleton {
    private static volatile Singleton singleton;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (singleton == null) { 
            synchronized (Singleton.class) {            //1
                if (singleton == null) {                //2
                    singleton = new Singleton();        //3
                }
            }
        }
        return singleton;
    }
}

对于为什么要加锁,相信大家都知道的吧,防止在多线程运行的时候得出来的结果跟自己预想的不一样,但是为什么要用到volatile呢?我们先说一下这个程序的大致执行过程,现在有两个线程a和b:

  1. 线程a首先进入getInstance()方法
  2. 这个时候由于singleton为空,所以线程a在1处进入synchronized代码块
  3. 这个时候线程b也开始攻占进入getInstance()方法
  4. 由于singleton还是空的,所以线程b试图获取获取1处的锁,但是线程a已持有该锁,所以线程b被阻塞
  5. 线程a继续执行,由于在2 处实例仍旧为 null,线程a就创建一个 Singleton 对象并将其引用赋值给 instance
  6. 此时线程a对出synchronized代码块并且返回相对应的实例
  7. 线程b获得锁之后继续执行
  8. 由于这时候singleton并非锁空的,所以没有创建第二个对象
  9. 结束

双重检查锁定背后的理论是完美的。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行 ,内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。

其实这个原因主要出现在实例化代码的时候,也就是singleton = new Singleton();
看似只是简单的一行代码,但是它并不具有原子性(什么是原子性呢,就是要么全部都执行,要不就都不执行,举个栗子:i++并不具备原子性,因为它是先+1,然后再去赋值的),这里面可细分为:

a. memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址

正常来说是按顺序执行的,也就是a-b-c酱紫,但是虚拟机会对这些代码进行指令重排序,也就是说有可能会出现a-c-b的情况,分配了相对应的内存指针后但是并未完成初始化,这个时候线程b进来,发现线程a(未完成初始化)并不是空的,然后就将没有完全初始化成功的singleton返回去了,导致程序出现问题;

所以我们就要加上volatile这个关键字了,因为它会禁止掉虚拟机的指令重排序,原原本本的按着正常的顺序去执行相对应的代码,这个问题也就搞定啦~~

你可能感兴趣的:(Android,Java,单例模式,java,开发语言)