volatile和内部类实现单例模式的原理(自我理解)

来源:互联网 发布:mvr蒸发器设计软件 编辑:程序博客网 时间:2024/05/16 18:50

之前一直对利用volatile实现单例模式的原理不太了解,今天看了一片关于volatile的文章,终于有些了解,在这里记录一下,顺便列一下对内部类实现单例的理解,避免忘记。
首先说一下关于多线程比较注意的几个点,原子性,可见性(共享变量修改之后其他线程能立即更新),有序性(处理器为提升效率,会在保证结果的情况,改变代码运行顺序,此处说的是单线程情况下)。通过synchronized和lock可以达到上面的方法。
volatile可以实现可见性,和有序性。volatile修饰的变量,在被修改之后会被强制写入主存,并使其他线程中的该共享变量的拷贝失效,只能从主存中读取最新的值,以此来保证可见性。但是注意,因为volatile不能保证原子性,所以对于一些非原子性的操作,例如下面的代码,输出结果不为10000。在inc做自增之前,先读取,多个线程读取的值并不是自增之后的值,两次自增可能还是从0变为1。

public class Test {    public volatile int inc = 0;    public void increase() {        inc++;    }    public static void main(String[] args) {        final Test test = new Test();        for(int i=0;i<10;i++){            new Thread(){                public void run() {                    for(int j=0;j<1000;j++)                        test.increase();                };            }.start();        }        while(Thread.activeCount()>1)  //保证前面的线程都执行完            Thread.yield();        System.out.println(test.inc);    }}

其次可见性的保证是volatile会禁止指令重排:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

(个人理解:volatile变量操作的相对位置不会改变,在其之前,之后的代码块内部会被指令重排,但是整体是会在其之前,之后执行,这也是后面实现单例的原因)

下面来看下要说的volatile的单例模式

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

变量初始化,分为几个步骤,分配内存,构造函数,赋值(引用),这三个操作有可能会被乱序,先分配内存,赋值,最后构造函数,所以在没有volatile修饰的情况下,有可能导致线程a已经分配内存,并赋值,但是暂时未执行构造函数,此时instance不为null,此时线程b阻塞a,判断instance不为null,返回不完整对象,程序执行就会出错。
加了volatile之后,正常的执行顺序分配内存,构造函数(操作变量),赋值(引用)不会被改变,即使线程被阻塞,在检查是否为null时不会出现之前的情况,也就能保证执行正确。

最后看下内部类的单例模式

public class Singleton { private Singleton() {} public static class Holder {  // 这里的私有没有什么意义  /* private */static Singleton instance = new Singleton(); } public static Singleton getInstance() {  // 外围类能直接访问内部类(不管是否是静态的)的私有变量  return Holder.instance; }}

在访问instance之前,内部类不会被加载,在访问时instance会和内部类同步加载,对于Singleton类来说已经做到了延迟加载。

参考文章:
1、Java并发编程:volatile关键字解析
2、单例模式与双重检测

0 0
原创粉丝点击