volatile为什么无法保证原子性?

1、示例

public class AtomicDemo2 {
    
    
  private static volatile  int count = 0;
      public static void inc(){
    
    
          try {
    
    
              Thread.sleep(100);
          } catch (InterruptedException e) {
    
    
              e.printStackTrace();
          }
          count++ ;
      }
    public static void main( String[] args ) throws InterruptedException {
    
    
          for(int i=0;i<1000;i++){
    
    
              new Thread(AtomicDemo2::inc).start();

          }
        Thread.sleep(4000);

        System.out.println("运行结果:"+count);

    }

} 

运行结果:

在这里插入图片描述

从JMM内存分析:

在这里插入图片描述

问题1: 为什么单个变量不用volatile修饰就会有问题?
这里说的单个变量不用volatile修饰的有问题的特指long和double类型修饰的变量(64bit)。在一些32位的处理器上,如果要求对64位数据的写操作具有原子性,会有比较大的开销,为了照顾这种处理器,Java语言规范鼓励但不强求JVM对64位的long型变量和double型变量的写操作具有原子性。当JVM在这种处理器上运行时,可能会把一个64位long/double型变量的写操作拆分为两个32位的写操作来执行。这两个32位的写操作可能会被划分到不同的总线事务中执行,此时对这个64位变量的写操作将不具有原子

问题2:为什么i++这种的用volatile修饰不能保证其原子性呢?
javap : 字节码查看
在这里插入图片描述

其实i++这种操作主要可以分为3步:(汇编)

  1. 读取volatile变量值到local
  2. 增加变量的值
  3. 把local的值写回,让其它的线程可见
mov    0xc(%r10),%r8d ; Load
inc    %r8d           ; Increment
mov    %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier

Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失。

史上最全的并发编程脑图:https://www.processon.com/view/5b1f1ad7e4b03f9d251c06e5#map

猜你喜欢

转载自blog.csdn.net/fd2025/article/details/108359770