首先,volatile的作用是保证内存的可见性,但是不能保证操作的原子性。
在Java中内存模型中,将内存模型分为主内存和工作内存。
主内存是对所有线程所共享的,而每个线程都有自己的工作内存(比如cpu缓存,寄存器等等都是一个原理的,都是为了加快读取速度),工作内存是不共享的。
在线程工作时,会从主内存中拷贝一份需要的变量放在工作内存。线程对变量的所有操作,都是先操作工作内存中的副本,然后由副本对主内存进行同步更新。
线程间变量值的传递需要通过主内存来完成,而工作内存的变量何时同步到主内存中,这个就是由JVM来控制的。
由于这种存储原理,在多线程中就会产生脏读(脏读就是读到的不是最新的数据)
比如:
int i = 0;
//线程A和线程B同时执行下面操作
i++;
正常来说,我们希望的值是2,但是输出的值不一定是2,这就是因为脏读。
原理是:A从主内存中拿去了一份i = 0;的拷贝到工作内存,在工作内存中进行i++;将i变为了1。但是在A将工作内存中的信息同步到主内存之前,B线程已经从主内存中拿了一份拷贝放在了工作内存,此时B线程工作内存中的i = 0;执行 i++;后i = 1;。这样两个工作内存中的i都为1,所以最后同步到主内存中的值就是1,最终结果就为1.
上述例子中,线程A和线程B的工作内存是不可见的,也就是两个互不干扰。
而volatile能实现内存的可见性。
所谓可见性就是指当一个线程修改共享变量,其他线程所获取的变量值一定是最新的。原理就是volatile关键字修饰的变量,会保证修改后的值会立即更新到主存中,同时其他线程工作内存中存储的备份会失效,当其他线程下次读取该变量时,将强制去主存中拿取。
那么问题来了,volatile能不能解决脏读问题呢?答案是不能,因为volatile不能实习原子性操作。
所谓原子性操作就是不可能再拆分的操作,要么执行,要么不执行。比如synchronized修饰的方法或者代码块就是原子性。在多进程(线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。简单理解,原子性就是同时只有一个线程可以对该资源进行操作,并且不能被中断。
而对于刚才的例子,即便volatile能保证线程A对i进行操作后立刻将主内存中的i变量进行同步更新,但是如果在A之前线程B已经获取到了那么就又会出现脏读的情况。
所以在多线程中使用volatile变量要避免以下2点:
1,对变量的更改操作不依赖当前的值
比如:i = 10;//这个赋值语句就和i的值没有关系,而i++就依赖i的值进行计算。
2.不能将变量应用在临界值判断上(上界或下界)
volatile与sychronized的区别
1.volatile只能使用在变量级别,sychronized可以使用在方法、代码块上
2.volatile不会造成线程阻塞,sychronized会造成线程阻塞
3.volatile不能够保证操作的原子性,sychronized可以保证操作的原子性