volatile关键字

先来一点铺垫

先看看内存模型的概念

cpu经常会读取和写入数据,但是cpu的运算速度很快,但是从主存读写数据的速度远远比不上cpu的
读写速度。所以中间就添加了一个高速缓存。主存把数据放到高速缓存中,cpu从高速缓存中读取,然后返回
给高速缓存。高速缓存再写入主存

如何解决多线程共享数据不一致

多线程访问共享数据如果没有一些手段,将会出问题如脏读。。
两种解决方案:加锁。缓存一致性
加锁:在cpu读取内存时。系统锁住该线程,并且不准其他cpu读取数据。直到该操作完成为止
缓存一致性:当cpu发现操作的变量是共享变量(其他cpu中存在副本)。就发出信号通知其他cpu将
该变量的缓存行设置为无效

[图片上传失败...(image-fdc73c-1518589180914)]

并发编程的三问题

原子性问题
可见性问题
有序性问题:处理器并不会按照顺序加载代码(如果两行代码没有依赖性)。可能会重排序,但是处理器会保证运算结果和初始顺序结果一样
在单线程情况下是没问题的。但是在多线程情况下就有问题了。

java内存模型解决三问题

原子性问题:对基本数据的读取和赋值是原子操作,更多的可用synchronized和lock
可见性问题:volatile关键字。被volatile关键字修饰的变量更新后将会马上更新到主存。其他
线程会去读新值。synchronized和lock也能保证
有序性问题:volatile能保证一点有序性。synchronized和lock保证有序性
            happen-before原则。不能遵从happen-before,虚拟机就可以对他们随意排序
            
 happen-before原则
 1.程序顺序原则。前一个操作结果对后一个操作是可见的。
 2.锁定规则:同一个锁的解锁 happen-before 同一个锁的加锁操作
 3.volatile规则:一个变量的写发生在写之前
 4.传递规则:a发生在b前,b发生在c前,则a发生在c前
 5.线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
 6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
 7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
 8.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

volatile的两层语义

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  
2)禁止进行指令重排序。

volatile不能保证原子性

volatile 会保证线程读到最新的值。但是是在没有发生堵塞的情况下
如果一个自增操作,在查询到值后,被堵塞。就变成了查询操作,也就不会无效其他cpu的缓存
其他线程就只能读到脏数据

volatile保证一定的有序性

volatile变量保证他前面的操作都已经进行,后面肯定没有进行

volatile的原理

加入volatile的代码会生成一个lock前缀
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  2)它会强制将对缓存的修改操作立即写入主存;

  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。    

你可能感兴趣的:(volatile关键字)