处理并发操作除了使用synchronized,还有一个效率比较高的关键字就是volatile
要想理解volatile必须要理解两个概念:
1、内存模型的概念
2、并发编程中的三个概念
一、内存模型的概念
大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。
也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。举个简单的例子,比如下面的这段代码:
i =i+1
当线程执行到这一句代码的时候,会发生如下一系列操作
从主内存中读取 i 的值-----复制一份到自己所在cpu的高速缓存----CPU将i进行加 1 操作----写入到自己的高速缓存----最后将高速缓存中i最新的值刷新到主存当中
这段代码在单线程当中是没有问题的,多线程就会发生问题,假如i的初始值为0,然后有两个线程来执行上面的代码,我们希望看到的结果是 i 的值变为2,但是结果并不一定是这样的,首先线程1开始执行代码,从主存中读取i的值0,复制自己的工作线程中进行加1 操作,这时线程2进来的也从主存中读取i 值,注意这时候 i 的值还是0,因为线程1还没有来的及将计算之后的值写入到主存,这样就会导致最终的结果是1
图1二.并发编程中的三个概念
在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。我们先看具体看一下这三个概念:
1、原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2、可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
//线程1执行的代码
int i =0;
i =10;
//线程2执行的代码
j = i;
用代码简单理解上面的定义就是说 初始 i = 0,然后修改到10,这时候j必须也要为10,就是说 i 修改后必须反映到 j 上面。
但是实际情况并不是这样的,j 有可能是0而不是10,因为 线程1在工作线程当中改了 i 的值后并不一定立即刷新到主存当中,所以 j 比一定就是10
3、有序性
即程序执行的顺序按照代码的先后顺序执行。
int a = 0;//1
boolean flag = true;//2
i = 1;//3
flag = false;//4
代码3写在代码4前面,看上去应该是3比4先执行,但实际上却不是的,因为在jvm中存在指令重排。
下面解释一下什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
要想解决并发编程必须满足上面这三个条件
好了,说了那么多铺垫,来到我们的重点 volatile
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义
一、保证了不同线程对这个变量操作的可见性,即一个线程修改了变量其他的线程获取这个变量的值时立马更新
二、禁止指令重新排序
也就是说volatile保证了可见性和有序性,但是不能保证原子性
volatile是如何实现可见性的:
当一个变量被volatile修饰的时候,然后对这个变量的值修改的时候,修改之后工作内存中新的值会立即更新主存中的值,并且在主存中将这个值标记为最新的,其他线程会跟这个标记进行对比来判断是否需要更新。
网友评论