zoukankan      html  css  js  c++  java
  • Java内存模型

    什么是Java内存模型

    结合我上一篇文章(如果还没阅读的可以先看看https://www.cnblogs.com/bolbo/p/10593810.html)已经总结出并发的原因的其中两个原因就是缓存导致的可见性和编译优化导致的有序性,那么解决这两个问题的最直接方法就是禁用缓存和编译优化

    但是这样就违背了设计这两个功能目的了,所以合理的方法就是按需禁用缓存和编译优化。那么具体是怎么个按需法呢?

    我们知道并发编程主要出现的问题就是对共享资源的处理,所以我们只要针对共享资源进行按需禁用即可,换句话说只有程序员才知道哪些是共享资源,什么时候需要禁用缓存和编译优化,所以只需要提供给程序员方法即可,那么具体是什么方法呢?

    这些方法包括volatilesynchronizedfinal三个关键字以及Happens-Before规则

    下面我们重点讲解这些方法

    volatile关键字

    volatile修饰的变量,线程在每次使用变量的时候,都会告诉编译器,不能使用CPU缓存,必须从内存中读取或者写入到内存中

    Happens-Before 规则

    前面一个操作的结果对后续的操作是可见的,约束了编译器优化行为,允许编译器优化,但是要求编译器优化后一定要遵守这个规则

    1. 程序的顺序性规则

      按照程序顺序,前面的操作Happens-Before于后续的任意操作
      下面的代码先执行write方法,然后执行reader方法

      class VolatileExample {
        int x = 0;
        volatile boolean v = false;
        public void writer() {
          x = 42;
          v = true;
        }
        public void reader() {
          if (v == true) {
            // 这里 x 会是多少呢?
          }
        }
      }
      

      这里如果没有规则,那么x的值有可能是0,也有可能是42;基于这个规则x=42的这个操作是HB于v=true的,所以在下面的判断中,x一定是等于42的

    2. volatile规则
      这个规则是指一个被volatile修饰的变量的写操作,Happens-Before于后续对这个变量的读操作

    3. 传递性
      如果AHappens-Before于B,同时BHappens-Before于C,那么A Happens-Before 与 C

    4. 管程中的锁规则
      对一个锁的解锁 Happens-Before 于后续对这个锁的加锁
      这里解释一下管程,在Java中管程就是synchronizedsynchronized就是Java里对管程的实现

    5. 线程start()规则
      主线程A启动子线程B,子线程B可以看到主线程A启动的子线程B前的操作

      Thread B = new Thread(()->{
        // 主线程调用 B.start() 之前
        // 所有对共享变量的修改,此处皆可见
        // 此例中,var==77
      });
      // 此处对共享变量 var 修改
      var = 77;
      // 主线程启动子线程
      B.start();
      
      
    6. 线程join()规则
      主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现的),当子线程B完成后(主线程A的join()方法完成),主线程可以看到子线程B的操作

      Thread B = new Thread(()->{
        // 此处对共享变量 var 修改
        var = 66;
      });
      // 例如此处对共享变量修改,
      // 则这个修改结果对线程 B 可见
      // 主线程启动子线程
      B.start();
      B.join()
      // 子线程所有对共享变量的修改
      // 在主线程调用 B.join() 之后皆可见
      // 此例中,var==66
      
      

    final关键字

    这个变量生而不变,很容易给我们引起误解
    比如下面这个例子

    // 以下代码来源于【参考 1】
    final int x;
    // 错误的构造函数
    public FinalFieldExample() { 
      x = 3;
      y = 4;
      // 此处就是讲 this 逸出,
      global.obj = this;
    }
    

    这个例子中,构造函数把this赋值给了global.obj,这里会因为编译器的重排序,出现this还没有初始化完成就已经复制给global.obj了

  • 相关阅读:
    [WPF系列] window自定义
    [WPF系列]-Prism+EF
    C#基础-事件 继承类无法直接引发基类的事件
    [WPF系列] 高级 调试
    [WPF系列]-DynamicResource与StaticResource的区别
    [WPF系列]-DataBinding 绑定计算表达式
    [WPF系列]- Style
    [WPF系列]-基础 TextBlock
    [WPF系列]-ListBox
    [WPF系列]-DataBinding 枚举类型数据源
  • 原文地址:https://www.cnblogs.com/bolbo/p/10598773.html
Copyright © 2011-2022 走看看