volatile变量
这是Java提供的一种弱同步机制;volatile变量有2种语义。
- volatile变量对所有线程均可见
- volatile变量禁止指令重排序
volatile变量对所有线程均可见
可见性是指:一条线程改变了变量的值,其他线程都能知道。
在解释这个规则原理之前,先对内存可见性做一定了解:
https://www.cnblogs.com/dhcao/p/10982278.html
java工作线程的内存称为线程的工作内存,线程之间的内存是独立的(Java栈空间是属于线程的,正是由于内存独立),线程之间通过主内存进行信息交换。
正是由于线程之间内存独立,而数据操作都是在线程的工作内存中进行。那么普通的变量就会出现并发安全问题。在2个线程同时从主内存中加载出来了a=1,并且线程1进行操作a=a+1;线程2进行操作a=a+2;那么最后主内存中的数据中,a到底是多少呢??
这取决于到底线程1还是线程2最后写入,肯定是后写入的值生效,将覆盖前面的值。
那么volatile变量是如何做到所有线程都能知道最新的值的呢?
如你所想;volatile变量在使用前,会进行刷新。
在2个线程同时从主内存中加载出来了a=1,并且线程1进行操作a=a+1;线程2进行操作a=a+2;那么最后主内存中的数据中,a到底是多少呢??
volatile变量的操作过程如下:线程1在执行a= a+1时,先刷新a的值,即重新从主内存中获取a的值;执行完之后,将a的值写入到主内存(立刻写入,不会重排序)。线程2在执行a=a+2时,也要先刷新a的值,这时假设a已经被线程1改变,那么线程2在执行之前,就会将a更新为新的值。保证了变量a的操作正确性!
volatile变量禁止指令重排序
普通变量能保证最后的结果如程序代码所描述,但不能保证底层实际的执行顺序如程序所写。
在解释这个规则原理之前,先对指令重排序做一定了解:
https://www.cnblogs.com/dhcao/p/10982278.html
指令重排序出现的前提:JIT对字节码进行编译得到汇编代码。所以如果程序运行在纯解释器环境(呵,这肯定是不可能的),是不存在重排序的现象的,毕竟都没有编译。
而将字节码编译成汇编,JIT会使用分层机制对需要编译的代码进行优化(也不扯太远),最终的结果是:编译成的汇编代码,跟肉眼可见的Java代码顺序不一定一致。详情见见上文博客;那是因为JIT会根据情况将代码打乱重分配,只要保证最后的结果符合happens-before原则。即不破坏程序的有序性!
而使用volatile修饰的变量,编译器和运行时都会注意这个变量是共享的,因此不会将该变量上的操作与其他操作仪器重排序。
那么是为什么呢,怎么才能做到不对它进行重排序呢。答案是采用内存屏障(Memory Barrier或Memory Fence)。对volatile变量的操作,都将使用内存屏障,而重排序时,不能将volatile变量后的操作排序到屏障之前。
内存屏障相关:如果了解内存屏障,推荐
- 《Java并发编程的艺术》
- 百度:内存屏障
都是些要记的东西,记住原理即可:重排序时不会打乱顺序。
总结
从volatile的语义我们知道,volatile并不是保证线程安全。而是变量共享!如果只有一个线程修改volatile变量,那么其他线程都能读到正确的值。
假设多线程同时写volatile的变量呢,类似上方的分析(线程1和线程2同时写入a的值,或者更多的线程同时写呢…如果将Java语句拆成字节码,我们在Java代码中的一行代码,可能得到多行字节码,同理一行字节码可能得到多行汇编…所以可见性不等于原子性),我们很容易就能得到volatile也是不绝对安全的,他不能保证操作的原子性。所以如果想要真正的并发安全(操作原子性),我们还是得使用synchornized。
当且仅当满足以下条件时,才应该使用volatile变量:
- 对变量的写操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
- 该变量不会与其他状态变量一起纳入不变性条件中。
- 在访问变量时不需要加锁。
推荐volatile变量用法:
作为标志位
/**
* @Author: dhcao
* @Version: 1.0
*/
public class useVolatile {
/**
* flag作为标志位,如果发生改变,即要通知所有线程
*/
volatile boolean flag = false;
public void add(){
while(!flag){
System.out.println("还不到改变时候...");
}
}
}
这只是一个演示,假设我们有操作:我们在商城活动中,记录下100个最先进入的ip地址作为幸运用户(不考虑ip重复)!我们可以增加一个计数器,当计数器达到100时,将flag修改为true,这样,就算多个线程同时进来,他们依然可以知道,已经有100个用户了。