zoukankan      html  css  js  c++  java
  • java 内存可见性

    java线程 -> 线程工作内存 -> 主物理内存

    线程工作内存的原理是栈内是连续的小空间,寻址速度比堆快得多,将变量拷贝到栈内生成副本再操作

    什么是重排序

    代码指令可能并不是严格按照代码语句顺序执行的。
    大多数现代微处理器都会采用将指令乱序执行的方法,在条件允许的情况,直接运行当前有能力立即执行的后续指令,避免造成等待(CPU的操作速度远快于与物理内存通信的速度),大大提高执行效率。JIT编译器也会做指令重排序操作。

    工作内存

    每个线程有一个栈,每个栈有一个工作内存,将共享变量读到工作内存后,线程使用的变量值就是自己栈内存中的变量副本了,此时主内存变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化。
    volatile修饰的变量,虚拟机会保证从主内存加载到工作线程的值是最新的。

    as if serial

    所有的操作都可以为了优化而进行重排序,但是最终的执行结果就像按顺序执行一样,如果有依赖关系,虚拟机会阻止重排序。
    异常处理:java异常处理机制也会为重排序做一些特殊处理,也就是插入错误补偿代码,将程序恢复到发生异常时应有的状态。

    内存可见性

    为了避免处理器访问主内存的时间开销,处理器大多会利用缓存提高性能,因此,处理器上的缓存和主内存的数据并不是实时同步的,各cpu间缓存的数据也不是实时同步的

    happens-before

    happens-before原则保证了我们的程序执行的可预测性,内存的可见性

    • 单线程程序代码次序法则
    • 监视器锁法则,对一个监视器锁的解锁 happens-before每一个后续的加锁
    • volatile变量法则
    • 线程启动法则:thead.start() happens-before 线程里面的动作
    • 线程中的动作happen before 线程终结或thread.join
    • 传递性原则

    内存屏障

    内存屏障 Memory Barrier 是一种CPU指令,用于控制特定条件下的重排序和内存可见性
    LoadLoad屏障 -> Load1; LoadLoad; Load2 load2及后续读取操作要读取的数据被访问前,load1要读取的数据被读取完毕
    StoreStore屏障 Store2及后续写入操作执行前,保证Store1的写入操作对其他处理器可见(刷新到内存)
    LoadStore屏障 load1数据装载,之前于Store及后续的存储指令刷新到内存
    StoreLoad屏障 保证Store的写入对所有处理器可见先于load操作。该屏障之前的所有内存访问指令(存储和load)完成之后,才执行屏障后的指令它的开销最大,兼具其他3种内存屏障的功能,因此它开销会很昂贵

    内存屏障的作用是禁止重排序,虽然表面上是禁用单线程执行的指令重排序,但是间接影响到多线程之前的指令重排序,如保证变量b被操作前,先于b的写操作一定能被其他线程访问到

    Thread A:
     a=1;
     b=true;
     
    Thread B:
     if(b){
       c = a + 1;
    } 
    

    volatile

    当一个操作是volatile写时,与前面的任何类型的读写都不能重排序,但可以与后面的普通读写重排序
    当一个操作是volatile读时,与后面任何类型的操作都不能重排序,与前面的普通读写可以重排序
    volatile读与写不能重排序
    volatile写使用StoreStore内存屏障,保证前面所有的普通写操作已经对任意处理器可见了,因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存

    当写入一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存
    当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,从主内存中读取所有的共享变量。

    class VolatileExample {
    	int a = 0;
    	volatile boolean flag = false;
    
    	public void writer() {
    		a = 1;                   //1
    		//StoreStore,a对flag可见
    		flag = true;               //2
    		//StoreLoad,flag和a对后续可见
    	}
    
    	public void reader() {
    		//LoadLoad,flag和a可见
    		if (flag) {                //3
    		//LoadStore,flag和a可见
    			int i =  a;           //4
    			……
    	}
    }
    

    编译器如果能证明volatile变量只能被单线程访问,那么就可能会把它作为普通变量处理
    使用volatile关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性

    final

    对final语义的扩展保证一个对象的构建方法结束前,所有final成员变量都必须完成初始化(的前提是没有this引用溢出)。

    CAS在JDK5中被J.U.C包广泛使用,在JDK6中被应用到synchronized的JVM实现中,因此在JDK5中J.U.C的效率是比synchronized高不少的,而到了JDK6,两者效率相差无几,而synchronized使用更简单、更不容易出错,所以它是专家组推荐的首选,除非需要用到J.U.C的特殊功能(如阻塞一段时间后放弃,而不是继续等待)。

    参考文章

    http://www.cnblogs.com/mengheng/p/3491092.html

    https://tech.meituan.com/MySQL_PingCAP_Practice.html

    http://www.cnblogs.com/chenyangyao/p/5269622.html
    https://blog.csdn.net/u011663071/article/details/78964991

  • 相关阅读:
    Java的 Annotation 新特性
    Java 枚举
    Java 泛型
    Linux kali信息探测以及 Nmap 初体验
    静态导入 ()
    Java foreach循环
    Java 可变参数
    炫酷的CSS3响应式表单
    关于CSS选择器连续性的问题
    简述ECMAScript6新增特性
  • 原文地址:https://www.cnblogs.com/windliu/p/10196949.html
Copyright © 2011-2022 走看看