聊聊Volatile的那些事

在谈Volatile之前需要先知道jvm内存的构造,所以我们先介绍一下JVM内存(声明:以下内容部分来自点击打开链接)。

JVM内存由两个部分组成,一个是主内存,另外一个是工作内存。

主内存主要包括本地方法区和堆

每个线程都有一个工作内存,工作内存中主要包括两个部分:属于该线程的栈和对主内存部分变量拷贝的寄存器

有以下需要注意的:

1、所有的变量都保存在了主内存中,主内存对所有线程都是共享的。

2、每个线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的操作是在工作内存中进行的,不能直接读写主内存中的变量。

3、线程之间无法直接访问对方的工作内存中的变量,需要通过主内存来传递。



JVM规范定义了线程对内存间交互操作:

Lock(锁定):作用于主内存中的变量,把一个变量标识为一条线程独占的状态。

Read(读取):作用于主内存中的变量,把一个变量的值从主内存传输到线程的工作内存中。

Load(加载):作用于工作内存中的变量,把read操作从主内存中得到的变量的值放入工作内存的变量副本中。

Use(使用):作用于工作内存中的变量,把工作内存中一个变量的值传递给执行引擎。

Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋值给工作内存中的变量。

Store(存储):作用于工作内存中的变量,把工作内存中的一个变量的值传递到主内存中。

Write(写入):作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。

Unclock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放出来,之后可被其他线程锁定。


java的多线程并发问题会直接反映在内存模型上,所谓线程安全无非就是要空值多个线程对某个资源的有序访问或修改,那么要做到这点就需要解决两个主要问题:有序性和可见性。

有序性:所谓有序性就是值多个线程之间按照先后顺序来访问操作资源。线程A和线程B同时要访问一块资源C,要么A先访问,要么B先访问,不能在A访问的时候B又去访问,这样会出问题。有一个经典的银行存款例子,一个银行账户存款100元,这时一个人从改账户取10元,同时另外一个人向该账号存10元,那么余额应该还是100元。那么此时发生如下情况,A线程负责取款,B线程负责存款,A从主内存中读到100,B从主内存中读到也是100,A执行+10操作,得到110,然后将该值从工作内存中同步到主内存中,此时主内存中的值为110元,而此时B执行-10操作,得到90,然后也将该值从工作内存中同步到主内存中,此时主内存里面的值为90元,这显然是错误的,由于线程A和线程B的操作是无序的(要么先存款后取款,要么先取款后存款),导致结果出现错误。

根据银行存款的例子我们来解析一下它的执行过程:

1、线程A从主内存中读取100到工作内存中,工作内存中的金额就是100

2、线程B从主内存中读取100到工作内存中,工作内存中的金额就是100

3、线程A在工作内存中将100加上10,变为110

4、线程A将110从工作内存中更新到主内存中,此时主内存中的金额变成110

5、线程B在工作内存中将100减去10,变成90

6、线程B将90从工作内存中更新到主内存中,此时主内存中的金额变成90

当然主内存中的金额除了是90也有可能是100,还有可能是110,每次执行结果都不一样,因为线程的执行顺序无法预料。要保证线程间对某个资源操作的有序性,就需要执行原子操作,而原子操作就要用到锁,例如synchronized就能够让多线程有序执行(当然也可以让多线程的内存可见)

在加了锁之后,我们再来看看上面的执行过程:

1、线程A获得同步锁,此时线程B被阻塞

2、线程A从主内存读取100到工作内存中,此时工作内存中的金额是100

3、线程A执行加10操作,此时工作内存中的金额变成110

4、线程A将110更新到主内存中,主内存中的金额就变成了110

5、线程A释放同步锁

6、线程B获得同步锁

7、线程B从主内存读取110到工作内存中,此时工作内存中的金额是110

8、线程B执行减10操作,此时工作内存中的金额变成100

9、线程B将100更新到主内存中,主内存中的金额就变成了100

10、线程B释放同步锁

当然,也可以是线程B先获得同步锁,但不管是哪一种情况,最终结果都是100,符合客观情况。


可见性:我们再来谈谈可见性,由于多个线程之间无法直接相互传递数据来通讯,需要通过共享变量,这个共享变量在主内存中,所谓的可见性就是某个线程修改了共享变量,其他线程能够看到被修改后的值,java中volatile解决了可见性的问题,但是volatile不能保证多线程的有序性,任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写到主存中。因此对于volatile修饰的变量的修改,所有线程马上就能看到。

猜你喜欢

转载自blog.csdn.net/weixin_39935887/article/details/80734211