赞
踩
1. volatile能保证变量在多线程之间的可见性。
2. volatile禁止CPU执行时进行指令重排操作(内存屏障)从而能保证有序执性。
3. volatile不能保证原子性。
内存可见性问题
内存可见性问题:在多线程共享一个数据块下,一个线程对数据进行修改操作时,其它线程是无法感知的。甚至会被编译器优化到完全不可见的程度。
例如:当两个线程同时操作一个内存,例如一个读一个写,但是当“写线程”进行的修改的时候,“读线程”可能读到修改前的数据,也可能读到修改后的数据,这是不确定的。
不可见的原因:
CPU为了提高数据获取速率,会设置缓存。
在多核CPU下,每个核都有自己的独占缓存进行数据存取,只有在所有处理结束后,才会将数据同步到主存中。
所以会导致有些核读取到的是过期的数据。
volatile的原理
简单来说:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。
当一个线程向被volatile关键字修饰的变量写入数据的时候,虚拟机会强制它被值刷新到主内存中。
当一个线程用到被volatile关键字修饰的值的时候,虚拟机会强制要求它从主内存中读取。
内部如何做到缓存主存同步的?
缓存一致性协议(MESI):当CPU写数据时,如果发现操作的变量时共享变量,即其他线程的工作内存也存在该变量,于是会发信号通知其他CPU该变量的内存地址无效。当其他线程需要使用这个变量时,如内存地址失效,那么它们会在主存中重新读取该值。
现象:
import java.util.concurrent.TimeUnit; public class Demo { // 保证内存可见性 public static volatile boolean flag = true; static class Thread1 extends Thread { @Override public void run() { while (flag) { // 一直循环,等待flag被改变 } System.out.println("线程1发现flag被改变了。。。"); } } static class Thread2 extends Thread { @Override public void run() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } flag = false; System.out.println("线程2修改了flag的值。。。"); } } public static void main(String[] args) throws InterruptedException { Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t2.start(); t1.start(); } }
指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果时正确的,但是无法保证程序的操作顺序与代码顺序一致。
这在单线程中不会构成问题,但是在多线程中就会出现问题。
非常经典的例子是在单例方法中同时对字段加入voliate,就是为了防止指令重排序。
private volatile static LazyModeV3 instance;
对象实例化的步骤:
memory = allocate(); // 1.分配对象内存空间
instance(memory); // 2.初始化对象
instance = memory; // 3.设置instance指向刚分配的内存地址,此时instance!=null
代码重排序后的步骤:
memory=allocate(); // 1.分配对象内存空间
instance=memory; // 3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory); // 2.初始化对象
volatile的原理
volatile禁止重排序是利用
内存屏障
,保证有序性。
内存屏障是一组CPU指令,用于实现对内存操作的顺序限制。
Java编译器在生成指令系列时,在适当的位置会插入内存屏障
来禁止处理器对指令的重新排序。
(1)volatile会在变量写操作的前后加入两个内存屏障,来保证前面的写指令和后面的读指令是有序的。
(2)volatile在变量的读操作后面插入两个指令,禁止后面的读指令和写指令重排序。
直接上代码,看现象:
public class Test { public static volatile int i = 0; static class Thread1 extends Thread { @Override public void run() { for (int j = 0; j < 1000000; j++) { i ++; } } } static class Thread2 extends Thread { @Override public void run() { for (int j = 0; j < 1000000; j++) { i --; } } } public static void main(String[] args) throws InterruptedException { Thread1 t1 = new Thread1(); t1.start(); Thread2 t2 = new Thread2(); t2.start(); t1.join(); t2.join(); System.out.println(i); // 结果是随机数,说明volatile不支持原子性 } }
总结:volatile不能保证原子性,要想保证原子性我们要使用锁机制。
总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是Java多线程的学习,深入学习了关键字volatile的作用以及原理。之后的学习内容将持续更新!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。