volatile是java 1.5提出的一个类型修饰符,用来保证变量的内存可见性及有序性(不可重排序),但无法保证操作的原子性。
Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存TLAB(比如CPU的寄存器),线程只能访问自己的工作内存,不可以访问其它线程的工作内存。工作内存中保存了主内存共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中。
可见性:
volatile保证可见性的原理是在每次访问变量时都会进行一次刷新,因此每次访问都是主内存中最新的版本。所以volatile关键字的作用之一就是保证变量修改的实时可见性。
以下代码中的volatile关键字的有无可以看出此特性。
package com.test.vol;
public class TestVolatile {
public static void main(String[] args) {
Demo d = new Demo();
new Thread(d).start();
while(true) {
if (d.isFlag()) {
System.out.println("see");
break;
}
}
}
}
class Demo implements Runnable {
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("now is " + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
有序性:
在JDK1.5之后,可以使用volatile变量禁止指令重排序。volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
无法保证原子性:
volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作“count++”等操作。
实现原理:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”。--《深入理解Java虚拟机》
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。