volatile关键字内存不可见问题最详细解释(与synchronized的区别)

谈到内存可见性,首先让我们看看在多线程下处理共享变量时Java的内存模型
共享内存模型
Java共享内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存中的变量复制到自己的工作空间或者叫工作内存中,线程读写变量时操作的是自己的工作内存中的变量。
Java内存模型是一个抽象的概念,下面来看一下实际的工作内存
实际工作内存
图中所示是一个双核CPU系统架构,每个核有自己的控制器和运算器,运算器执行算术逻辑运算。每个核有自己的一级缓存,在有些架构里面还有一个所有CPU都共享的二级缓存。Java内存模型里面的工作内存,实际上就是L1或者L2缓存。
当一个线程操作共享变量时,它首先从主内存中复制共享变量到自己的工作内存,然后对工作内存里的变量进行处理,处理完后将变量更新到主内存中。
那么假如线程A和线程B同时处理一个共享变量X,会出现什么情况?我们使用上图所示的CPU架构,假设线程A和线程B使用不同的CPU执行,并且当前两级Cache都为空,那么这时候由于Cache的存在,将会导致内存不可见问题,具体看下面的分析:

  • 线程A首先获取共享变量X的值,由于两级Cache都没有命中,所以加载主内存中X的值,假如为0。然后把X=0的值缓存到两级Cache,线程A修改X的值为1,然后将其写入两级Cache,并且刷新到主内存线程A操作完毕后,线程A所在的CPU的两级Cache内和主内存里面的值都是1。
  • 线程B获取X的值,首先一级缓存没有命中,然后看二级缓存,二级缓存命中了(如上图,L2 Cache是公用的),所以返回X=1,到这里一切都是正常的,因为这时候主内存中也是X=1。然后线程B修改X的值为2,然后把它存放在线程B所在的一级Cache和共享的二级Cache中,最后更新到主内存中X的值为2,到这里也一切都是正常的。
  • 线程A又需要修改X的值,获取时一级缓存命中,并且X=1,到这里问题就出现了,明明线程B已经把X的值修改为了2,为何线程A获取的还是1呢?这就是共享变量的内存不可见问题,也就是线程B写入的值对A不可见。

那么如何解决共享变量内存不可见问题?使用Java中的volatile关键字就可以解决这个问题,下面详解:
首先看synchronized关键字,synchronized块是Java提供的一种原子性内置锁,这些内置的使用者看不到的锁被称为内部锁。当进入到synchronized中时,即获取到锁(加锁)后,会清除工作内存(L1/L2缓存),这样就只能从主内存中获取值了。退出synchronized时,即释放锁后,会立刻把修改过的值刷新到主内存中,除了可以解决共享变量内存的可见性问题外,synchronized经常被用来实现原子性操作。另外请注意,synchronized关键字会引起线程上下文切换并带来线程调度开销,所以很笨重

主角volatile登场:
对于解决内存可见性问题,Java还提供了一种轻量级形式的同步,也就是使用关键字volatile。该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在L1/L2 Cache或者寄存器等其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存值但是volatile虽然保证了共享变量的可见性,但是它不能保证操作的原子性。
注:原子性是指在一个操作中就是CPU不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。

什么时候使用volatile呢?
写入变量值不依赖变量的当前值时。例如:a++,分为3步,获取当前a的值,然后a+1,然后写入主内存,因为这三步操作不是原子性的,而volatile不保证原子性。

什么时候不使用volatile呢?
如果读写的变量加锁了,那就没必要使用volatile了,因为加锁本身已经保证了内存的可见性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值