欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

volatile为什么不保证原子性?为什么Atomic保证原子性?

程序员文章站 2022-06-21 13:43:22
volatile前言举个栗子原因错误理解 (不想看就跳过)真正原因解决措施为什么AtomicXXX保证原子性前言大家都知道,volatile是具有可见性的。即是在多线程操作下,volatile修饰的共享变量会在更新操作之后,每个线程获得最新的值。不了解volatile的实现原理可以参考:博客链接那么问题来了,既然可以保证修改后的变量可以被立即更新,那么为什么不能保证操作的原子性呢?举个栗子private volatile int i = 0;i++;如果在多线程操作下,i++调用10次,那...

前言

大家都知道,volatile是具有可见性的。即是在多线程操作下,volatile修饰的共享变量会在更新操作之后,每个线程获得最新的值。
不了解volatile的实现原理可以参考:博客链接

那么问题来了,既然可以保证修改后的变量可以被立即更新,那么为什么不能保证操作的原子性呢?

举个栗子

private volatile int i = 0;
i++;

如果在多线程操作下,i++调用10次,那么i的值会是10吗?答案是小于10的,因为会有操作被覆盖。

原因

i++的操作是分三步实现的。

  1. 线程获取 i 的值
  2. temp = i+1
  3. i = temp(写回主存)

错误理解 (不想看就跳过)

如果此时i的值是5,此时线程A获取了 i 的值,并且执行到了第二步。然后线程A阻塞,轮到线程B执行。B执行同样的操作,获取到 i = 5,然后执行自增操作并且写回主存(此时i = 6)。然后轮到线程A执行将自增完成的 i 值(i = 6)写回主存。

这样,虽然进行了两次自增,但是 i 的值只增加了一次,不具备原子性。

( 更新完变量后写回主存不是立即执行的,这个时候如果对volatile变量执行操作,保证不了原子性)虽然我很想这样说,但是这个是错误的理解

因为assign store write这三个原子指令是连续的。也就是说,volatile修饰的变量被修改完成之后,会被立即写回主存,其他变量可以立即看到新值。(图片来自尚硅谷)
volatile为什么不保证原子性?为什么Atomic保证原子性?

真正原因

根据缓存一致性,一个处理器的缓存回写到主存会导致其他处理器的缓存失效。当线程A,B同时执行volatile int i的自增的时候,先执行完的线程A回写数据到主存,导致B的缓存变量无效(因此执行+1操作已经没有意义),直接从主存读取最新的值,这样就相当于少做了一次+1。
volatile为什么不保证原子性?为什么Atomic保证原子性?

自称从美团并发构架师的人那学来的无敌图(但是被我偷了)@博主void丿

解决措施

1.使用总线锁保证原子性

所谓总线锁就是使用处理器提供一个LOCK#信号,当一个处理器在总线输出此信号的时候,其他处理器将被阻塞,该处理器独共享内存

2.使用缓存锁保证原子性

在同一时刻只需要保证某个内存地址的操作是原子性即可。

3.如果是int类型 i++这样的自增操作,直接使用AtomicInteger(保证原子性)

为什么AtomicXXX保证原子性

Atomic使用了volatile修饰value,然后一直使用CAS保证原子性。(其实还用到了native)
看一下Atomicxxx的源码,这里是AtomicLong

public class AtomicLongextends Numberimplements java.io.Serializable {
    private volatile long value;
 
    /**
     * Creates a new AtomicLong with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicLong(long initialValue) {
        value = initialValue;
    }
 
    /**
     * Creates a new AtomicLong with initial value {@code 0}.
     */
    public AtomicLong() {
    }

其中value是用volatile修饰的

CAS源码

int compare_and_swap (int* reg, int oldval,int newval)
{
  ATOMIC();
  int old_reg_val = *reg;
  if (old_reg_val == oldval) 
     *reg = newval;
  END_ATOMIC();
  return old_reg_val;
}

CAS是乐观锁,如果发现寄存器旧值发生了改变,说明有CPU在修改,重新进行这次操作。用CAS一直判断 就可以保证随时获得最新的值,保证操作的原子性。

都看到这里了,还不点个赞支持支持吗 (ノへ ̄、)

本文地址:https://blog.csdn.net/glum_0111/article/details/107672828