zoukankan      html  css  js  c++  java
  • 第一部分:并发理论基础02->java内存模型,看java如何解决可见性和有序性问题

    1.前情提要

    可见性,原子性,有序性,称为并发编程的bug之源

    2.java的内存模型

    导致可见性问题是cpu缓存引起
    导致有序性问题是编译器优化
    那么解决方案是什么?
    禁用缓存和禁用编译优化,但是程序性能就下降了

    那么如何能保证性能的同时,又解决了可见性及有序性问题?
    该禁用缓存和编译优化的时候禁用,其余时候不做限制

    java内存模型规范了jvm如何按需禁用缓存和按需禁用编译优化的方法。
    主要包含volatile,synchronized,final三个关键字,以及6个happens-before规则

    3.volatile

    volatile int x = 0
    告诉编译器,对这个变量x的读写,不能使用cpu缓存,必须从内存中读取或写入

    示例代码思考

    
    // 以下代码来源于【参考1】
    class VolatileExample {
      int x = 0;
      volatile boolean v = false;
      public void writer() {
        x = 42;
        v = true;
      }
      public void reader() {
        if (v == true) {
          // 这里x会是多少呢?
        }
      }
    }
    

    线程A执行write方法,volatile会把变量v=true写入内存,假设线程B执行reader()方法,按照volatile,线程B会从内存中读取变量v,线程看到v==true时,变量x是多少?
    jdk1.5之后是42,jdk1.5之后对volatile进行了升级

    4.Happens-Before规则

    前面一个操作的结果对后续操作是可见的
    happens-before规则榆树了编译器的优化行为

    4.1程序的顺序性规则

    按照程序顺序,前面操作happens-before与后续任意操作。
    x=42,happens-before与v=true,符合单线程里面的思维,程序前面对某个变量的修改一定是是对叙操作可见的

    4.2 volatile规则

    对volatile变量的写操作,happens-before与后续对这个volatile变量的读操作
    有点禁用缓存的意思

    4.3 传递性

    A happens-before B,且B happens-before c,那么A happens-before c
    image

    x=42,happens-before与写变量v=true,这是4.1程序顺序性规则的内容
    v=true happens-before 读变量v==true,这是4.2 volatile规则

    根据传递性原则
    x=52 happens-before 读变量vtrue
    如果线程B读到了v
    true,那么线程A设置的x=42对线程B是可见的。

    4.4 管程中锁的规则

    锁的解锁happens-before与后续对这个锁的加锁操作

    管程是java中提供的同步原语,就是synchronized关键字

    
    synchronized (this) { //此处自动加锁
      // x是共享变量,初始值=10
      if (this.x < 12) {
        this.x = 12; 
      }  
    } //此处自动解锁
    

    结合4.4的规则,我们可以判断出如果x的初始值是10,线程A执行完代码库后x的值会变为12,然后自动释放锁,线程B抢到锁后,能够看到线程A对x的写操作,也就是说线程B可以看到x==12

    4.5 线程start()规则

    线程A调用线程B的start方法,那么该start操作happens-before与线程B中的任意操作

    
    Thread B = new Thread(()->{
      // 主线程调用B.start()之前
      // 所有对共享变量的修改,此处皆可见
      // 此例中,var==77
    });
    // 此处对共享变量var修改
    var = 77;
    // 主线程启动子线程
    B.start();
    

    4.6 线程join规则

    线程等待,主线程A等待子线程B完成,调用join,子线程B完成后,主线程能够看到子线程的操作
    看到的是对共享变量的操作

    线程A调用线程B的join()并成功返回,那么线程B的任意操作happens-before 与该join操作的返回

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

    5.final

    final 关键字可以告诉编译器优化的更好一点
    final修饰变量时,告诉编译器,这个变量生而不变,可劲优化

    现在final 修饰的变量,对编译重排进行了约束,不会导致有序性问题并产生空指针异常

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

    5.总结

    happens-before 本质上是一种可见性

    共享变量abc,在一个线程里设置了abc的值=3,那么有什么办法可以让其他线程看到abc==3

    1.声明共享变量abc,并使用volatile关键字修饰
    2.声明共享变量abc,在synchronized关键字对abc的赋值代码库加锁,由于happen-before管程锁规则,后续线程可以看到abc的值
    3.A线程启动后,使用A.JOIN()方法来完成运行,后续线程再启动,就一定可以看到abc==3
    
    原创:做时间的朋友
  • 相关阅读:
    为上次写的框架加了一个辅助功能
    复制文件夹下所有文件
    进制之间的相互转换
    c# 修改appConfig文件节点
    GUID
    太悲哀了
    poj2411 Mondriaan's Dream
    poj3311 Hie with the Pie
    HDU3001 Travelling
    luogu p2622关灯问题II
  • 原文地址:https://www.cnblogs.com/PythonOrg/p/14930658.html
Copyright © 2011-2022 走看看