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

之前一直对利用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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值