volatile关键字

一、基本概念

volatile作为Java虚拟机提供的最轻量级同步机制,用于保证共享变量在多线程的情况下各线程获取相同,不出现对该变量的操作和其他内存操作一样重排序。

重排序

在虚拟机上,由于内存操作速度远小于CPU的操作速度,为了减少CPU在等待内存操作过程的时间,虚拟机会按照一定规则打乱指令的执行顺序,

int a = 1;
int b = 0;
int c = a+b;

这三行除了第三行需要在前两行之后执行,前两行在虚拟机中的执行顺序是随机的,这就是在保证一定顺序情况下的指令重排序。《深入理解Java虚拟机》的12节有更深入解释,可以看一下。
而对于volatile来说,其中有一个规则是对于它同步机制的保证,就是volatile修饰的变量,写操作先行发生于后面对于这个变量的读操作,意思是在出现读取变量和写入变量操作时候,写入优先于读取,具体会在下面解释。

内存模型

由于volatile主要是对于内存运用,需要简单解释一下Java并发过程中对内存模型的三个性质以及在volatile的体现。
可见性
可见性保证共享变量在多线程情况下值的准确性。指当一个线程对共享变量进行修改操作的时候,其他对该变量有操作的线程可以立即获取这个修改。volatile使用的方式是在修改的同时立即同步到主内存中并将其他内存中的变量值无效化,使用之前需要再次从主内存中获取。
原子性
原子性只要指对于原子性变量的load、read、assign、use、store和write保证原子性,这里指基本数据类型的访问读写。
有序性
由于在虚拟机中存在指令重排序的问题,因此存在synchronize和volatile关键词来保证部分操作禁止指令重排序。

二、实现原理

在大致了解这个关键词的作用之后,需要关注的就是它的实现原理,它是如何保证共享变量的同步的。

内存模型

被volatile修饰的共享变量在多线程的情况下变量内存使用模型如图
image1
共享变量会被创建在主内存中,也就是多线程的公共存储区,而多个线程在访问该变量的时候是将其先从内存拷贝变量到CPU缓存中,在对该值进行操作。同时该变量会有两个特性:

  • 多线程的可见性,如上面所说,使用共享变量的线程对变量的值进行操作之后,CPU缓存立即同步到主内存中,并将其他线程在CPU缓存的变量值无效化,强制让其他线程再次读取主内存的值,实现同步机制。
  • 禁止指令重排序,如上面所说,带有volatile的变量写操作先行发生于后面对于这个变量的读操作,多个线程操作变量时,优先处理写操作。

实现机制

在加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,对变量所在内存加上内存屏障(作用是保证编译器在内存访问上有序),保证变量操作之前的操作在虚拟机中一定在之前执行,

缺点

其实大家也注意到了,volatile并不能保证变量的原子性,它保证的是对变量的读取和写入的原子性,但是对于中间变量的操作并不能保证,如下面的例子

public class VolatileTest {
	public volatile int i = 0;
	public void add() throws InterruptedException {
	//增加线程执行时间
    	Thread.sleep(1000);
    	i++;
    	System.out.println(i);
}

	public static void main(String[] args) {
    	final VolatileTest test = new VolatileTest();
    	for(int j = 0;j<10;j++){
        	new Thread(){
            	@Override
            	public void run() {
                	for(int j = 0;j<10;j++)
                    	try {
                        	test.add();
                    	} catch (InterruptedException e) {
                        	e.printStackTrace();
                    	}
            	}
        	}.start();
    	}
	}
}

正常来说结果应该是100,但是每次结果都小于100,因为在自增的过程不是同步的,假设a线程读取了i值为1,进入i+1的操作时,b线程也读取了i,此时并无写入操作,i任然是1,之后a线程加1结束写入主内存2,但是由于b线程已经在加1操作,写入操作怼它无影响,结果还是2,写入主内存之后还是2,但是实际上,此时应该为3。因此,volatile逐渐被淘汰减少使用了。


这里感谢反应弧超长啊的提醒,我没有考虑到main线程提前执行,现在对代码进行了修改。

原文地址:https://www.cnblogs.com/xudilei/p/6833241.html