JMM---Volatile关键字

JMM
        即Java Memory Model 它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等

JMM体现在以下几个方面
        ·原子性-保证指令不会受到线程上下文切换的影响(一个线程内多行代码以一个整体运行,期间不能有其它线程的代码插队)

                synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

        ·可见性-保证指令不会受cpu缓存的影响(一个线程对共享变量修改,另一个线程能看到最新的结果)
                volatile关键字可以保证可见性

        ·有序性-保证指令不会受cpu指令并行优化的影响(一个线程内代码按编写顺序执行)
                
在Java里面,可以通过volatile关键字来保证一定的“有序性”(volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
                另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。



可见性
     
  先来看一个现象 main线程对run变量的修改对于t线程不可见 导致了t线程无法停止
                主线程先运行 主线程运行之后 新建的线程才运行 新建的线程还没有进行while()循环呢
主线程就结束了 所以要让主线程sleep一下 不然run=false对while循环不起作用

import static java.lang.Thread.sleep;

public class VOASD {
    static boolean run = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (run){

            }
        }).start();
        sleep(1);
        run = false;//线程t不会如预想的停下来
    }
}

分析:
        
1.初始状态 t线程刚开始从主存读取了run的值 到工作内存

         2.因为t线程要频繁从主内存中读取run的值(while循环)   JIT编译器会将run的值缓存至自己工作内存中的 高速缓存中 减少对主存中run的访问 提高效率

         3.1秒之后 main线程修改了run的值 并同步至主存 而t是从自己工作内存中的高速缓存中读取这个变量的值 结果永远是旧值        解决办法:
        volatile static boolean run = true; 
                加了volatile关键字之后的变量就不能从缓存中读取了 必须从主内存中找最新值

        volatile(易变关键字)
                它可以用来修饰成员变量和静态成员变量 可以避免线程从自己的工作缓存中查找变量的值 必须到主存中获取它的值 线程操作volatile变量都是直接操作主存
 


可见性VS原子性
     
  前面的例子体现的实际就是可见性 它保证的是在多个线程之间 一个线程对volatile变量的修改对另一个线程可见 不能保证原子性 仅用在一个写线程 多个读线程的情况
上面的例子从字节码理解是这样的

         比如线程安全时 举的例子 卖票问题 或者 两个线程一个i++ 一个i-- 只能保证看到最新值 不能解决指令交错           volatile只能保证 getstatic读到的是最新的值

           synchronized语句既可以保证代码块的原子性 也同时保证代码块内变量的可见性 但缺点是
synchronized是属于重量级操作 性能相对更低
                

有序性 
       
JVM会在不影响正确性的前提下 可以调整语句的执行顺序 思考下面一段代码
        
        可以看到 至于是先执行i还是先执行j 对最终的结果不会产生影响 所以 上面代码真正执行
时 既可以是
        
也可以是
                
        这种特性称之为[指令重排]   多线程下指令重排会影响正确性

指令重排的前提是 重排指令不能影响结果

Volatile关键字
         volatile 是java虚拟机提供的轻量级同步机制
             
特性:

        可见性

        不保证原子性

        禁止指令重排

 volatile的底层原理是内存屏障:
     
  ·对volatile变量的写指令后会加入写屏障
        ·对volatile变量的读指令后会加入读屏障


如何保证可见性
       
写屏障保证在该屏障之前的 对共享变量的改动 都同步到主存当中

        读屏障保证在该屏障之后 对共享变量的读取 加载的是主存中最新数据

 

如何保证有序性
       
写屏障会确保指令重排序时 不会将写屏障之前的代码排在写屏障之后

         

         读屏障会确保指令重排序时 不会将读屏障之后的代码排再读屏障之前

 

volatile不能解决指令交错(不能保证原子性)
        写屏障仅仅是保证之后的读能读到最新的结果 但不能保证读跑到它前面去
        而有序性的保证也只是保证了本线程内相关代码不被重排序

 synchronized可以同时解决有序 可见 原子性

原子性举例:
        static volatile int balance = 10;
             
   t1线程是balanc+=5;
                t2线程是balanc-=5;

但因为volatile 不能保证原子性 所以可能t1线程刚执行过getstatic 就切换到了t2

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值