竹笋

首页 » 问答 » 问答 » 是轻量级的synchronized
TUhjnbcbe - 2024/6/22 1:23:00

volatile有两大特性:保证内存的可见性和禁止指令重排序。那什么是可见性和指令重排呢?接下来我们一起来看。

内存可见性

要了解内存可见性先要从Java内存模型(JMM)说起,在Java中所有的共享变量都在主内存中,每个线程都有自己的工作内存,为了提高线程的运行速度,每个线程的工作内存都会把主内存中的共享变量拷贝一份进行缓存,以此来提高运行效率,内存布局如下图所示:

但这样就会产生一个新的问题,如果某个线程修改了共享变量的值,其他线程不知道此值被修改了,就会发生两个线程值不一致的情况,我们用代码来演示一下这个问题。

以上程序的执行结果如下:

flag被修改成true

我们会发现永远等不到检测到flag变为true的结果,这是因为非主线程更改了flag=true,但主线程一直不知道此值发生了改变,这就是内存不可见的问题。

内存的可见性是指线程修改了变量的值之后,其他线程能立即知道此值发生了改变。

我们可以使用volatile来修饰flag,就可以保证内存的可见性,代码如下:

以上程序的执行结果如下:

检测到flag变为trueflag被修改成true

指令重排

指令重排是指在执行程序时,编译器和处理器常常会对指令进行重排序,已到达提高程序性能的目的。

比如小强要去图书馆还上次借的书,随便再借一本新书,而此时室友小王也想让小强帮他还一本书,未发生指令重排的做法是,小强先把自己的事情办完,再去办室友的事,这样显然比较浪费时间,还有一种做法是,他先把自己的书和小王的书一起还掉,再给自己借一本新书,这就是指令重排的意义。

但指令重排不能保证指令执行的顺序,这就会造成新的问题,如下代码所示:

以上程序执行结果如下所示:

可以看出执行到次时发生了指令重排,y就变成了非正确值0,显然这不是我们想要的结果,这个时候就可以使用volatile来禁止指令重排。

以上我们通过代码的方式演示了指令重排和内存可见性的问题,接下来我们用代码来演示一下volatile同步方式的问题。

volatile非同步方式

首先,我们使用volatile修饰一个整数变量,再启动两个线程分别执行同样次数的++和--操作,最后发现执行的结果竟然不是0,代码如下:

以上程序执行结果如下:

可以看出,执行结果并不是我们期望的结果0,我们把以上代码使用synchronized改造一下:

这次执行的结果变成了我们期望的值0。

这说明volatile只是轻量级的线程可见方式,并不是轻量级的同步方式,所以并不能说volatile是轻量级的synchronized,终于知道为什么面试官让我回去等通知了。

volatile使用场景

既然volatile只能保证线程操作的可见方式,那它有什么用呢?volatile在多读多写的情况下虽然一定会有问题,但如果是一写多读的话使用volatile就不会有任何问题。volatile一写多读的经典使用实例就是CopyOnWriteArrayList,CopyOnWriteArrayList在操作的时候会把全部数据复制出来对写操作加锁,修改完之后再使用setArray方法把此数组赋值为更新后的值,使用volatile可以使读线程很快的告知到数组被修改,不会进行指令重排,操作完成后就可以对其他线程可见了,核心源码如下:

总结

本文我们通过代码的方式演示了volatile的两大特性,内存可见性和禁止指令重排,使用++和--的方式演示了volatile并非轻量级的同步方式,以及volatile一写多读的经典使用案例CopyOnWriteArrayList。

1
查看完整版本: 是轻量级的synchronized