volatile是如何是实现可见性的

比如现在我们有这样一段代码:线程等待另一个线程将数据装载完就输出success,可是最后程序一直卡在while循环里没有往下执行。

public class VolatileDemo {
    private static boolean flag = false;
    //private static volatile boolean flag = false;

    public static void main(String[] args) throws Exception{
        new Thread(()->{
            System.out.println("等待装载数据。。。。");
            while(!flag){
            }
            System.out.println("====== SUCCESS =====");
        }).start();
        Thread.sleep(2000);
        new Thread(()->{
            System.out.println("开始装载");
            flag = true;
            System.out.println("装载完毕");
        }).start();
    }
}
/* 控制台输出
        等待装载数据。。。。
        开始装载
        装载完毕
 */

造成这个问题出现的原因是jmm原子操作造成的。jmm内存模型就是java内存模型、准确的说是java线程内存模型。它和cpu缓存模型类型、是基于cpu缓存模型来建立的。
jmm一共有8种原子操作:
  read(读取):从主存读取数据
  load(载入):将内存数据读到工作内存
  use (使用):取出工作内存中的数据来计算
  assign(赋值):将计算好的值重新赋予到工作内存中
  store(存储):将工作内存数据写入主存
  write(写入):将store过去的变量值赋值给主内存中的变量
  lock(锁定):将主内存变量加锁,标识为线程独占状态
  unlock(解锁):将主存变量解锁,解锁后其他线程可以锁定该变量

  

可以看到线程1已经把变量副本加载到工作内存了,而线程2将计算后的值存到主存之后,却没有办法告诉线程1,所以就出现了线程安全问题。其实cpu与主存交互会经过"总线"这么一个概念,cpu为了解决这种数据不一致问题有两种方案:
总线加锁(性能太低)
  早期cpu是对总线加锁,lock住这个数据,这样其它线程就没法对它读或写,直到这个线程用完这个数据 unlock之后才能被其他线程操作。也就是说从read开始后直到write结束才释放锁。
MESI缓存一致性协议
  多个线程将同一个数据读取到各自的缓存区后,某个cpu修改了缓存的数据之后,会立马同步给主存,这都是汇编语言实现的。其他cpu通过总线嗅探机制(可以理解为监听)可以感知到数据的变化从而将自己缓存里的数据失效,从而去读取主存的值。所以mesi协议是同store开始加锁,锁的粒度更小,时间更短。实际上volatile就是这么实现可见性的。同时由于这中间过程中有store和write几步操作、还要让其他cpu缓存的数据置空都是要耗时的,可能这个过程中数据被别人改了,所以他是非原子操作的。

猜你喜欢

转载自www.cnblogs.com/wlwl/p/11920689.html