说起volatile, 经常听到的一句话就是使用了该关键字修饰的变量在线程中可见.
那么为什么不使用volatile的变量在线程中不可见?
首先来一段代码演示:
代码中bo设置了false了还一直在循环中.是为什么?
这得从java内存模型中说起.
JAVA内存模型及操作规范
-
共享变量必须存放在主内存中.
-
线程有自己的工作内存,而且只可操作自己的工作内存
-
线程要操作共享变量,需从主内存中读取到工作内存,改变值后需从工作内存中同步到主内存中
当线程2对共享变量bo改变后其它线程并不知道,加volatile关键字就是起到修改可见性,就是一个线程对主内存的变量修改了其它线程能立马知道,怎么实现的?
发送一条lock指令给cpu,cpu在计算完之后会立即将这个值写回主内存,而其它线程利用嗅觉机制感知到自己工作内存的某个共享变量已被其它线程更改,会致为失效,下次使用会从主内存读取
因为在加一个线程能读到这个bo的值为false,这里主要是sleep(2)这段代码,睡眠了2秒钟后在改变的bo=false,那前面的bo当然就一直在循环啊,如果前面的bo一直读不到false,那就是JIT做了优化,它认为你这个一直循环取的值都是相同的,达到一个循环阈值那他就直接把字节码替换成本地代码,哪怕你后来改成了false也与这个线程里的值无关了,所以会一直是true
下面我们来加上volatile关键字:
加上关键字后,JIT就不会去优化了,因此就实现了可见性
多线程有可能出现指令重排序,就是指令不按代码的先后顺序,例如对象还没有构造完就已经把对象赋值给了变量,那程序就会出问题,volatile会把写操作和读操作添加内存屏障,写操作阻止其它的写操作越过屏障(本来该变量指令上面的却到了下面,多个变量时用volatile修饰的变量放在最后),读操作是添加向上的内存屏障,避免其它读操作越过该变量到上面去(可能变量还没有创建好,多个变量时用volatile修饰的变量放在最前), 用volatile修饰可以保证有序性
在做测试的时候发现一个奇怪的问题:
public class volatileClass {
private static boolean bo=true;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
int index=0;
while (bo){
// System.out.println(Thread.currentThread().getName()+"==="+(++index)+bo);
}
}
},"t1").start();
//TimeUnit.SECONDS.sleep(3);
new Thread(new Runnable() {
public void run() {
bo=false;
// System.out.println(Thread.currentThread().getName()+"==="+bo);
}
},"t2").start();
}
}
执行结果是:
就相当于加了synchronized一样,加synchronized也能实现线程可见性
请注意,volatile保存可见性,有序性,不保存原子性.