多线程之可见性:volatile
一.什么是volatile,它的作用是什么
我们知道线程安全的三大核心就是原子性、可见性和顺序性,synchronized可以实现原子性,而volatile就是来实现可见性。
那么什么是可见性呢?
我们知道,在现代计算机中,由于 CPU 直接从主内存中读取数据的效率不高,所以都会对应的 CPU 高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。而在多线程的情况下,可能会导致变量的值改变,与实际的值不符,而缓存里的值是不会改变的。所以应当保证在多线程的情况下,变量的值得改变要同步,volatile保证了每次值都从内存中获取。
可见性就是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
这里列举一个例子:
//线程一执行的代码
int i=0;
i=10;
//线程二执行的代码
int j=i;
- volatile 关键字就是用于保证内存可见性,当线程A更新了 volatile 修饰的变量时,它会立即刷新到主线程,并且将其余缓存中该变量的值清空,导致其余线程只能去主内存读取最新值。
- 使用 volatile 关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。
-
volatile不会引起线程上下文切换和调度,所以它可以理解为是轻量级的synchronized。
二.volatile的实现原理是什么
- volatile本身不具备原子性,只是保证了可见性。所以要注意当修改实例变量中的数据,比如:i++,也就是i=i+1,这样的一个过程就不是一个原子操作,也就是非线程安全的。i++的执行过程如下:
- 从内存中取出 i 的值;
- 计算 i 的值;
- 将 i 的值写到内存中。
- 这里如果在第二步的时候,另外一个线程修改了 i 的值,那么就会出现脏数据。这个时候我们可以通过synchronized这个关键字来解决,保证线程的原子性,也就是数据同步。所以说volatile并不能保证原子性,只能保证数据可以更新到主内存。
如上面这个图,我们可以得出以下结论:
1.read和load阶段:从主内存复制变量到当前线程的工作内存;
2.use和asign阶段:执行代码,改变当前共享变量值;
3.store和write阶段:刷新工作内存中的数据到主内存对应变量的值。
在多线程环境中,use和asign这两个阶段是会多次出现的,但是这一操作又不是原子性,也就是在read和load阶段后,如果主内存的count变量发生多次修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,也就是私有内存和公共内存中的值不同步,所以计算出来的结果会与预期的不一样,也就是会出现线程不安全问题。
对于volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的变量都是最新的,即只能保证read和load阶段数据的同步,当在use和asign阶段修改了变量的值之后,并不能及时更新到主内存,所以并不能保证数据的同步,这个时候还是需要通过synchronized等保证原子性的关键字来保证数据的同步。
此外,使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
三.volatile和synchronized有什么区别
最明显的区别就是:volatile保证了可见性,synchronized保证了原子性。但是其实synchronized也可以保证一定的可见性的,因为它会将私有内存和公共内存的数据做同步。总而言之,volatile解决的是多个线程之间的可见性,而synchronized解决的是多个线程之间资源的同步性。
此外,因为volatile是轻量级的synchronized,所以相对来说,它的性能应该会比synchronized好一点。
https://www.cnblogs.com/dolphin0520/p/3920373.html
《Java多线程编程的核心技术》
https://blog.csdn.net/qq_34337272/article/details/79680771