天天看点

可见性丶原子性和有序性

先说一下概念:

  • 可见性:

    一个线程对共享变量的修改,另外一个线程能够立刻感知到,我们称为可见性;

  • 原子性:

    一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性;

  • 有序性:

    指令重排导致顺序被打乱;

  线程工作内存: 是指 Cpu 的 ‘寄存器’ 和 ‘高速缓存’,线程的 working memory 是cpu的寄存器和高速缓存的抽象描述,数据读取顺序优先级 是:寄存器->高速缓存->内存

可见性:

线程工作空间导致可见性问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hNCfYp7P-1570757836145)(https://github.com/lantaoGitHub/photos/blob/master/Concurrency/%E5%85%B1%E4%BA%AB%E5%8F%98%E9%87%8F%E5%8F%AF%E8%A7%81%E6%80%A7.png?raw=true)]

  例如:线程A在主存中年将变量one=0拉去到自己的工作内存中,然后做了one = 5,当然这个操作是在cpu的寄存器中进行的,然后写会高速缓存中,这时线程A的高速缓存还未执行同步主内存的操作,线程B又将one=0从主存拉取到了线程B的工作内存中,导致A线程已经更新但是B线程看不到的可见性问题;

原子性:

线程切换导致原子性问题 ++count

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1NoRLBJh-1570757836147)(https://github.com/lantaoGitHub/photos/blob/master/Concurrency/%E7%BA%BF%E7%A8%8B%E5%88%87%E6%8D%A2%E5%AF%BC%E8%87%B4%E5%8E%9F%E5%AD%90%E6%80%A7%E9%97%AE%E9%A2%98++count%20.png?raw=true)]

  例如:当线程A从主内存中将共享变量Count加载到线程A的工作内存后,发生了线程切换,这个时候线程B也将共享变量Count从主内存加载到了线程B的工作内存,这时线程A和B的工作内存中count都是0,线程B执行了Count = Count + 1,然后写回到主内存,这时候线程切换完成,回到了线程A再次执行 Count = Count + 1,再将线程A工作内存计算过的count写回主内存,现在我们得到的主内存呢中Count值是1而不是2。

有序性:

指令重排导致有序性问题;

在这里讲一个例子,就是获取单例双重检查锁(double-checked locking)判断:

/**
 * @Auther: lantao
 * @Date: 2019-03-28 14:32
 * @Company: 随行付支付有限公司
 * @maill: [email protected]
 * @Description: TODO
 */
public class Test1 {
    
    private DoMain doMain;
    
    public DoMain getDoMain(){
        if(doMain == null){
            synchronized (this.getClass()){
                if(doMain == null){
                    doMain = new DoMain("");
                }
                return doMain;
            }
        }else{
            return doMain;
        }
    }
}

           

  在上边的代码中在synchronized内和外都有一个if判断,判断doMain是否为null操作,有很多人对synchronized中的if null判断不理解,其实可以这样想,线程A和线程B都执行到了synchronized这里进行竞争锁,结果A得到锁,判断if null,结果还未实例化,继续进行实例化,然后return对象并释放锁,这时线程B获取到了锁进入if null判断,发现doMain已经被线程A实例化过了,直接返回实例即可,第二个if null的作用就在这里;

看上去上边的代码是完美的,但是new的操作上我们理解是:

  • 创建内存M
  • 在内存M上初始化doMain对象
  • 将内存M的地址指向变量doMain

但是实际上优化后(指令重排)的执行路径可能是这样的:

  • 创建内存M
  • 将内存M的地址指向变量doMain
  • 将内存M的地址指向变量doMain

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4Phn3Ou-1570757836147)(https://github.com/lantaoGitHub/photos/blob/master/Concurrency/%E6%9C%89%E5%BA%8F%E6%80%A7%E9%97%AE%E9%A2%98.png?raw=true)]

上图大家应该都看的明白,我就不写了,本文主要就是让作者加深印象,本文是参考:https://time.geekbang.org/column/article/83682 总结的

可见性丶原子性和有序性

继续阅读