volatile缓存可见性底层原理

【问题提出】:如何解决多线程之间共享变量的可见性,一致性问题?

一、Volatile缓存可见性

(1)JMM内存模型:主内存和工作内存

Java线程内存模型跟CPU缓存模型类似,是基于CPU缓存模型建立的:
如图,有多个线程运行在不同CPU上,同时读取一个static变量或者实例变量,为了提高程序运行速度,会把主内存中的共享变量加载到工作内存中去。
在这里插入图片描述
——所以,如果在一个线程中改变了静态变量,另一个线程很有可能察觉不到这个改变
eg:

public class VolatileVisibilityTest {
    private static boolean initFlag = false;

    public static void main(String[] args) throws InterruptedException{
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("waiting data...");
                while(!initFlag){
                }
                System.out.println("======success");
            }
        }).start();
        Thread.sleep(2000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                prepareData();
            }
        }).start();
    }
    public static void prepareData(){
        System.out.println("preparing data...");
        initFlag = true;
        System.out.println("prepare end...");
    }
}

在这里插入图片描述
——线程一中有一个死循环等待,initFlag如果为false,就会一直等待,知道initFlag为true时才会显示Success;
但是当线程二中将initFlag改为true后,线程一还没结束循环,这就证明了线程二改掉的是加载到其工作内存中的静态变量,即便它会被存储回主内存中,线程一也不会去读取这个修改后的值

【解决方案】对变量加一个volatile关键字

    private static volatile boolean initFlag = false;

在这里插入图片描述

(2)为什么加了volatile的变量副本间能相互感知?

① Java内存底层交互模型:

  • read(读取):从主内存读取数据;

  • load(载入):将主内存读取到的数据写入工作内存;

  • use(使用):从工作内存读取数据来计算;

  • store(存储):将工作内存数据写入主内存

  • write(写入):将store过去的变量值赋值给主内存中的变量

  • lock(锁定):将主内存变量加锁,标识为线程独占状态;

  • unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量。

  • ② 早期volatile缓存可见性底层实现(总线加锁:性能低)
    第一个到达主内存的线程read操作之前,会对主内存加一把锁(lock),直到数据同步回主内存,才会把锁释放掉(unlock)。在这期间,其他线程无法读取(read)主内存共享变量,当锁被释放掉后,主内存的共享变量的值已经被同步为最新的值(即initFlag=true),其他线程再读取,就是最新数据。——线程之间要等待
    在这里插入图片描述
    ——性能太低!

③ MESI缓存一致性协议
多个CPU从主内存读取同一个数据到各自的高速缓存,当其中某个CPU修改了缓存里的数据,该数据会立马同步回主内存,其他CPU通过总线嗅探机制可以感知到数据的变化,从而将自己缓存里的数据变为失效

当CPU读自己的工作内存中的数据时,发现数据已经空了,就会马上重新从主内存中执行read操作,解决了可见性问题。
在这里插入图片描述
——为了使过程变得更快,lock操作需要进一步解释,见下文。

(3)Volatile缓存可见性底层实现原理(lock & MESI)

底层实现主要通过汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定),并写回到主内存。

IA-32架构软件开发者手册对lock指令的解释:
① 会将当前处理器缓存行的数据立即写回到系统内存;
② 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效(MESI协议)。

store之前加锁(lock)了,而不是在read前加锁(这个在主内存中的写入操作非常快,因而这个锁很快就会结束)。当需要被存储(store)的数据通过总线后,此时就会被嗅探到,会导致其它线程失效了,这些线程再去读主内存数据时,因为加锁了,需要等待数据写回(write)操作完成后,释放锁(unlock),其他线程才能去读取主内存中的数据,避免过早去读取。——缓存一致性

简单归纳总结:

(1)所有volatile修饰的变量一旦被某个线程更改,必须立即刷新到主内存;

(2)所有volatile修饰的变量在使用之前必须重新读取主内存的值;

二、并发编程的三大特性:可见性、原子性、有序性

volatile保证可见性与有序性,但是不保证原子性,保证原子性需要借助synchronized这样的锁机制。

可见性:共享变量被同时操作的时候,两个线程之间能够相互感知到变量的修改。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值