并发编程(二)---关键字volatile
volatile内存语义
valatite是java虚拟机提供的轻量级的同步机制。valatile有关键字有如下两个作用:
- 保证被volatile修饰的共享变量对所有线程总数可见的,也就是当一个线程修改了被volatile修饰的值,另外一个线程能立即得知。
- 禁止指令重排
volatile的可见性
关于volatile的可见性作用,我们必须意识到被volatile修饰的变量对所有线程总数立即可见的,对volatile变量所有写操作总是能立即反应到其他线程中
看以下代码,a线程修改flag值后会立马通知到线程b,去掉volatile后代码会出现死循环。
public class test {
volatile boolean flag=false;
public void save(){
this.flag=true;
}
public void load(){
while (!flag){
}
System.out.println(flag);
}
public static void main(String[] args) throws InterruptedException {
test test=new test();
Thread threada=new Thread(()->{
test.save();
});
Thread threadb=new Thread(()->{
test.load();
});
threadb.start();
Thread.sleep(1000);
threada.start();
}
}
volatile无法保证其原子性
public class VolatileVisibility {
public static volatile int i =0;
public static void increase(){
i++;
}
}
在并发环境下,i变量的任何改变都回立马反应到其它线程中,但是多条线程同时调用increase()方法的话,就会出现线程安全问题,毕竟i++操作不具有原子性,该操作是先取值,然后再写回一个新值,相当于原来的值加一,分两步完成,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会和第一个线程一起看到同个值,并执行相同值加1的操作,这就造成了线程安全失败。因此必须用synchronized方法修饰,以确保线程安全,需要注意的是一旦使用synchronized修饰方法后,由于synchronized本身也具备与volatile相同的特性,即可见性,因此在这样的情况下就完全可以省去volatile修饰变量。
volatile禁止重排优化
volatile关键字另一个作用就是禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。
以下是JMM针对编译器制定的volatile重排序规则表。
class VolatileBarrierExample {
int a;
volatile int v1 = 1;
volatile int v2 = 2;
void readAndWrite() {
int i = v1; // 第一个volatile读
int j = v2; // 第二个volatile读
a = i + j; // 普通写
v1 = i + 1; // 第一个volatile写
v2 = j * 2; // 第二个 volatile写
}
}