聊聊我对于Volatile的理解

聊聊我对 Volatile的理解

说之前,需要先了解下计算模型

CPU运算速度很快,内存的运算速度与CPU 元算速度相差很多倍,cpu在计算时,需要从速度与之相差不多的Cache取数据 寄存器>L1>L2>L3 (速度对比) ,CPU在操作数据时,数据从内存读到L3->L2->L1->寄存器 ,cpu处理完数据 从寄存器->L1->L2->L3->内存

JMM模型(Java Memory Model简称JMM)

jmm只是一种根据计算机模型抽象出的一种规范,是围绕原子性,可见性,有序性展开的,这个规范控制程序变量在共享区域,私有区域的访问方式,我认为,这个规范是Java可以在不同平台运行的根本,“一次编译,多处运行”
在这里插入图片描述

CPU操作数据流程

 下面每一个操作都是具有原子性的
  1. read: 把变量值 a 从主内存传输到线程的工作内存中
  2. load: 把read操作从主内存中得到的变量值放入工作内存的变量副本中
  3. use: 把工作内存中的一个变量值传递给线程1
  4. assign:把操作后的值赋值给工作内存中的变量
  5. store:把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
  6. write:把store操作变量,刷新到主内存
    在这里插入图片描述
    问题:线程1和线程n,同时对变量a做写操作,就会出现并发问题,
    解决方式:
    ① 对主内存中的变量a进行加锁(总线加锁)
    ② volatile修饰变量a

总线加锁:对主内存中的变量a 加锁,其他线程要使用到a,需要等到线程1操作完,释放对象锁. 线程②才可以去拿到变量a,去做操作

缺点:锁力度太大,效率低

volatile:在store过程lock 刷新到内存unlock

优点:锁力度小,效率高

围绕volatile可见性,谈谈我的理解

一写多读的情况
首先看段代码

public class VolatileVisibilityTest {


    private boolean flag = true;

    public void refresh() {
        this.flag = false;
        String name = Thread.currentThread().getName();
        System.out.println("线程"+name + "修改了共享变量initFlag");
    }

    public void load() {
        String name = Thread.currentThread().getName();
        while (flag) {

        }
        System.out.println(name + "当前线程感知到initFlag改变");
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileVisibilityTest visibilityTest = new VolatileVisibilityTest();
        Thread a = new Thread(() -> {
            visibilityTest.refresh();
        }, "线程a");
       //线程B 做空循环,线程A修改共享变量flag 值
        Thread b = new Thread(() -> {
            visibilityTest.load();
        }, "线程b");
        b.start();
Thread.sleep(200);
        a.start();
    }
}

图解如下
在这里插入图片描述

多写的情况如图:
在这里插入图片描述

我有个疑问:如果同一时间,两个线程都去修改变量a为M状态,哪个线程说了算???

一个指令周期内,会对指令进行裁决,所以不可能同时出现两个线程都修改为M,具体硬件是如何裁决的,这就要去关注cpu 怎么裁决的,我就不太晓得了

如果线程n裁决胜利,线程1是否会立刻从内存中去读取?

裁决失败(实际是就是把数据的缓存行置为失效)
不一定,取决与这段程序指令,是否要求再去读变量a,比如线程线程1要做两次循环,比如第一次裁决失败,还会再次去读取

缓存行(Cache line)是什么?有多大?

缓存行就是cpu 缓存的最小存储单元,可能有32字节 ,64字节 128字节,不同厂家cpu 缓存行大小不一 ,类似于机械硬盘最小存储单元是 簇 是一个道理

什么情况下MESI协议会失效???

如果变量存储长度超过一个缓存行,MESI协议就会失效,会加总线锁(对主内存变量加锁)

围绕Volatile的有序性谈谈我的理解

先看下图:
在这里插入图片描述
图中的红色过程都是原子操作,一定是顺序执行,但是不保证连续执行

先说结论:被volatile修饰后 read load use 操作 和assign store write 操作必须是连续的 也就是说read load use 这三个原子操作被捆绑到一起了 必须一起执行,assign store write 这三个操作也是同样的道理

为什么volatile可以保证有序性呢???

答:内存屏障可以保证不会发生指令重排

我理解的内存屏障?

内存屏障是cpu 提供的一些指令集,实现对内存操作的顺序限制
有下面几种类型

  1. LoadLoad屏障(读读):对于这样的语句Load1; LoadLoad; Load2,在Load2读取前,Load1必须读取完成
  2. StoreStore屏障(写写): 对于这样的语句Store1; StoreStore; Store2,在Store2写数据之前,Store1的写操作必须对Store2操作可见
  3. LoadStore屏障(读写): 对于这样的语句Load1; LoadStore; Store2, 在Store2写操作被刷到主内存之前,必须等Load1读取数据已完成
  4. StoreLoad屏障(写读): 对于这样的语句Store1; StoreLoad; Load2, 在Load2读取之前,必须保证Store1写入操作对Load2可见

问题:为什么Volatile对于一写多读是安全的?多写情况下不能保证线程安全?

答:Volatile 不能保证原子性,导致在多写情况下会出现线程不安全问题

围绕Volatile不能保证原子性,谈谈我的理解

看图:

上面说到过 ,lock,read,load,use,assign,store,write,unlock,这个八个都是原子操作

在这里插入图片描述
被Volatile修饰后.可以保证read,load,use操作和assign,store,write为原子操作,
但是不能保证原子操作A和B连续执行
如图,
在这里插入图片描述

如果Volatile和CAS滥用会带来什么问题?

总线风暴

如果你有不同见解,请在评论区留言交流吧

总结:Volatile 可以保证数据的可见性,有序性,不能保证原子性,可以保证一写多读情况下的线程安全

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值