理解volatile

作用:

1. 保证共享变量可见性

可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存

2. 禁止指令重排 

指令重排是指处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性

程序执行到volatile修饰变量的读操作或者写操作时,在其前面的操作肯定已经完成,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行


共享变量:可被多个线程共享的变量。在java中包括方法区与堆中的所存储的变量。eg.实例字段,静态字段,构成数组对象的元素。

java内存区域:指JVM中的几个内存区域,分别是方法区、堆栈、虚拟机、本地方法栈、程序计数器。区别于java内存模型。

java内存模型(JMM):一种抽象概念,一种规范。其定义了程序中线程对共享变量的访问方式。(一个线程对共享变量的写入何时对另一个线程可见)

扫描二维码关注公众号,回复: 1670227 查看本文章

    java内存模型包括主内存线程的工作内存。其中主内存存储共享变量,可供所有线程访问。而在每个线程内部都有一个工作内存,用于存放线程私有的数据。线程对变量的操作都必须在工作内存中进行,首先将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存。问题:若线程A修改变量x后还未刷新到主存,此时线程B去主存中读取变量旧的x进行操作,将产生错误结果。此为线程可见性问题。(可见性:当一个线程对共享变量进行修改后,另一线程可以读到该共享变量的最新修改值。)

    这里写图片描述

为解决该问题需要先理解:原子性、有序性、指令重排,happend-before规则。

原子性:一个操作要么完全执行,要么不执行。在多线程的环境下,指一个操作一旦开始,就不会被其它线程干扰,直至该操作执行完成。决不会以出现执行一半的操作被另一个线程所干扰的情况。(1、反例i++为非原子操作。2、32位的机器对64位数据的读写)

指令重排:计算机在执行程序时,为了提高性能,在不改变单线程程序语义,且不存在数据依赖性的情况下,编译器和处理器常常会对指令做重排。指令重排只会保证单线程中串行语义的执行的一致性,但并不会关心多线程间的语义一致性。

有序性:指令重排会改变指令的执行顺序,单线程中不会对最终结果产生影响,但在多线程环境中,会影响到多线程并发执行的正确性

volatile只保证在变量的可见性,不能保证变量操作的原子性。因为尽管使用了volatile关键字,但是如果对变量的操作是非原子操作,也会出现错误结果。此时可使用synchronized关键字,保证操作的原子性,以保证线程安全。

volatile是通过何种方式保证变量可见性的?

Memory Barrier,内存屏障(Lock前缀指令)。它是一个CPU指令。它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面(但不保证屏障前后内部指令顺序)。禁止指令重排,强制刷出各种cpu cache。

如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。

只能保证一定程度上的有序性。对于被volatile修饰的变量A可以保证有序性,与变量A无关的代码无法保证

通过设置内存屏障(lock操作)保证变量A的有序性。加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2)它会强制将对缓存的修改操作立即写入主存;
  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

指令重排典型:



参考:全面理解java内存模型及volatile你真的了解volatile关键字吗

猜你喜欢

转载自blog.csdn.net/xybz1993/article/details/79971152