文章目录
从Java内存模型出发
在Java内存模型中,每个线程,都有一个工作内存,它们与主内存交换数据。
现在主内存有一个数据data=0,线程1和线程2都需要用到这个data,就复制到工作内存当中。线程1和线程2的工作内存现在各有一份data数据。
为什么要这么做,而不是直接从主内存去读取data?
平时开发的Java代码,在运行时,都会翻译为机器指令给CPU执行,每次执行时的变量如果都从主内存中去加载,性能就会很差,为什么会很差?因为随着技术的发展,CPU的执行速度越来越快,内存的读取写入速度却发展缓慢,现在CPU的执行速度比内存的读取写入速度要快很多,所以就导致CPU总是等待内存读取写入数据,耗时。
那要怎么解决这个问题?
为了能够解决CPU和内存的性能问题,人们想出来了一个好的办法,就是在 CPU 和内存之间增加高速缓存,可以类比于JMM中的工作内存。
缓存的概念大家都知道,就是保存一份数据拷贝。它的特点是速度快,内存小,并且价格昂贵。那么,程序的执行过程就变成了:程序在运行过程中,会将运算需要的数据从主存复制一份到 CPU 的高速缓存当中。
那么 CPU 进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
简单来说的话,因为为了照顾CPU的性能,减少等待的时间,所以需要先加载数据到工作内存,运算结束在刷新到主内存中。
不过,这样虽然解决了性能上的问题,但是又引发了并发的问题。
Java并发问题
可见性问题
线程1修改了data=1,线程2和主内存依然只看到data=0(此时data没有立刻刷新会主内存),只有线程1能够读取到最新值,这就是可见性问题。
这就导致,线程1和线程2虽然都是在操作一个变量data,但是线程1修改了data变量的值之后,线程2是看不到的,一直都是看到自己本地工作内存中的一个旧副本的值!
用理论的话来描述可见性问题:
多个线程并发读写一个共享变量的时候,有可能某个线程修改了变量的值,但是其他线程看不到!也就是对其他线程不可见!
volatile 关键字的作用
那么如何解决上面说到的可见性的问题?这时候需要用到volatile关键字了。
1. volatile关键字使修饰变量强制刷回主内存
public class HelloWorld{
private volatile Integer data = 0;
}
一旦data变量定义的时候前面加了volatile来修饰的话,那么线程1只要修改data变量的值,就会在修改完自己本地工作内存的data变量值之后,强制将这个data变量最新的值刷回主内存,必须让主内存里的data变量值立马变成最新的值
2.violatile关键字修饰会让其他副本失效过期
如果此时别的线程的工作内存中有这个data变量的本地缓存,也就是一个变量副本的话,那么会强制让其他线程的工作内存中的data变量缓存直接失效过期,不允许再次读取和使用了
总结:
以上就是演示了volatile关键字如何解决JMM的可见性问题。
第一,就是强制修改后的变量副本刷回主内存,这样的话主内存中的值就能够保持最新
第二,让其他线程的变量副本过期,从主内存重新加载最新的值。
题外话
上面仅仅讨论了volatile关键字 能够解决可见性问题。
volatile关键字不仅能够解决可见性问题,还能保证有序性,但是并不能解决Java并发的原子性问题。
如果多个线程同时去修改data,那么最终会导致修改值混乱,volatile关键字是无法解决这样的场景的。
原子性问题需要用到synchronized关键字,ReentrantLock等加锁机制来解决。
参考
https://zhuanlan.zhihu.com/p/91621286
https://mp.weixin.qq.com/s/DmE9yyehx02YRsnlBAB1Fg