背景
假如有两人往同一个账户存钱,当其中一个人从账户中取走一部分时,另一方在不查看账户的情况下是不知道账户的金额有变动的,会认为账户中的金额并未发生变化,于是就有了这个问题(共享数据的变化不透明)
从JVM的角度理解问题所在
在jvm中,堆内存是唯一的,而栈内存并不唯一,每个线程都有自己的线程栈
当一个线程(A)要获取共享变量的值时,会访问堆内存,拿到共享变量的值,然后将这个值存储到自己的线程栈中,称之为变量副本。此后使用这个共享数据时,该线程会访问自己本地的变量副本,这样设计的好处是提升了获取数据的效率
而当另一个线程(B)修改过共享数据的值时,由于A线程本地变量副本的值没有改变,将导致A、B中记录的共享变量的值不同
那么,A会去重新访问堆内存中的共享数据吗?
答案是:会,但是什么时候去访问,我们无法控制
为了方便理解,附上图解
volatile关键字的作用与用法
作用:
强制线程每次在使用的时候,都会看一下共享区域最新的值
用法:
在共享变量前面加上volatile关键字就好嘞
使用synchronized同步代码块实现相同功能
同步代码块的作用中有一条和volatile关键字的效果一致
那就是强制线程查看共享变量的最新值
原子性
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。
常考问题:i++是非原子性的操作
原因:i++的操作一般分为三步
其中每一步都有可能被其他的线程抢走cpu的资源,导致最终数据出错
volatile关键字无法保证原子性
volatile关键字只能保证线程每次使用的共享数据是最新值,但是不能保证原子性,中途cpu的执行权任然有可能被夺走
如何保证原子性
使用atomic包下的方法,以原子方式对值进行操作,保证所有的操作都是原子性的,保证操作过程中不会有别的cpu介入,最后的值也会是稳定可控的。
比如AtomicInteger原子操作类