zoukankan      html  css  js  c++  java
  • java并发编程实战《二》java内存模型

    Java解决可见性和有序性问题:Java内存模型

    什么是 Java 内存模型?

    Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为,

    Java 内存模型规范了 JVM 如何提供按需禁用缓存编译优化的方法。具体来说,这些方法包括 volatile、synchronized 和 final 三个关键字,以及六项 Happens-Before 规则。

    Happens-Before 规则:前一个操作的结果对后续操作可见。

    前面一个操作的结果对后续操作是可见的
    前面一个操作的结果对后续操作是可见的
    • 程序的顺序性规则
      程序的顺序性规则
      程序的顺序性规则
      程序的顺序性规则
      程序的顺序性规则
      •   在一个线程中,按照程序顺序,对前部分代码的操作对后面的操作是可见的
    • volatile规则
      •   对volatile变量的写相对于volatile变量的读可见
    • 传递性规则
      •   a对b可见,b对c可见,则a对c可见
    • 管程中锁的规则
      •   对一个锁的解锁对后续对这个锁的加锁可见
    • 线程 start() 规则
      •       指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。
      •   
         1 package com.jeek_time.java并发编程实战.二.happensbefore;
         2 
         3 /**
         4  * 测试happens-before中的start规则
         5  * 主线程中start子线程B,在start B线程时可看到主线程对共享变量的操作, 0 -> 1
         6  */
         7 class TestStart{
         8     static int i=0;
         9     public static void main(String[] args) {
        10         i=1;
        11         final Thread subThreadB = new Thread(() ->{
        12             System.out.println(TestStart.i);
        13         });
        14         subThreadB.setName("子线程B");
        15         subThreadB.start();
        16     }
        17 }
    • 线程 join() 规则
      •  指主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作(共享变量)
      •     下面这段代码的结果:先0后1,刚好可印证结论,很有趣。 
      •  1 package com.jeek_time.java并发编程实战.二.happensbefore;
         2 
         3 import lombok.SneakyThrows;
         4 
         5 /**
         6  * 测试happens-before中的join规则
         7  * 指主线程等待子线程 B 完成(主线程通过调用子线程 B 的 join() 方法实现),
         8  * 当子线程 B 完成后(主线程中 join() 方法返回),主线程能够看到子线程B的操作(共享变量)
         9  */
        10 public class TestJoin{
        11     static int i = 0;
        12     @SneakyThrows
        13     public static void main(String[] args) {
        14         final Thread subThreadB = new Thread(() ->{
        15             TestJoin.i = 1;
        16             try {
        17                 Thread.sleep(1000);
        18             } catch (InterruptedException e) {
        19                 e.printStackTrace();
        20             }
        21         });
        22         subThreadB.setName("子线程B");
        23         subThreadB.start();
        24 
        25         System.out.println("返回前:" + TestJoin.i);
        26         subThreadB.join();
        27         System.out.println("返回后:" + TestJoin.i);
        28     }
        29 
        30 }
    final
      final 修饰变量时,初衷是告诉编译器:这个变量生而不变,可以可劲儿优化。
      问题类似于上一期提到的利用双重检查方法创建单例,构造函数的错误重排导致线程可能看到 final 变量的值会变化。
      在 1.5 以后 Java 内存模型对 final 类型变量的重排进行了约束。现在只要我们提供正确构造函数没有“逸出”,就不会出问题了。
     
      比如,在构造函数里面将 this 赋值给了全局变量 global.obj,这就是“逸出”,线程通过 global.obj 读取 x 是有可能读到 0 的。因此我们一定要避免“逸出”。
      
    2 final int x;
    3 // 错误的构造函数
    4 public FinalFieldExample() { 
    5   x = 3;
    6   y = 4;
    7   // 此处就是讲this逸出,
    8   global.obj = this;
    9 }
     

      Happens-Before 规则最初是在一篇叫做 Time, Clocks, and the Ordering of Events in a Distributed System 的论文中提出来的,在这篇论文中,Happens-Before 的语义是一种因果关系。在现实世界里,如果 A 事件是导致 B 事件的起因,那么 A 事件一定是先于(Happens-Before)B 事件发生的,这个就是 Happens-Before 语义的现实理解。
      在 Java 语言里面,Happens-Before 的语义本质上是一种可见性,A Happens-Before B 意味着 A 事件对 B 事件来说是可见的,无论 A 事件和 B 事件是否发生在同一个线程里。例如 A 事件发生在线程 1 上,B 事件发生在线程 2 上,Happens-Before 规则保证线程 2 上也能看到 A 事件的发生。
     
    课后思考
    有一个共享变量 abc,在一个线程里设置了 abc 的值 abc=3,你思考一下,有哪些办法可以让其他线程能够看到abc==3?
    •   声明共享变量abc,并使用volatile关键字修饰abc
    •   声明共享变量abc,在synchronized关键字对abc的赋值代码块加锁,由于Happen-before管程锁的规则,可以使得后续的线程可以看到abc的值。
    •   A线程启动后,使用A.JOIN()方法来完成运行,后续线程再启动,则一定可以看到abc==3

    摘自极客时间王宝令老师的课程

  • 相关阅读:
    对ArcGis Engine的增、删、改实现
    修改 ArcGis Engine 图层字段值
    获取DataTable 删除行的数据
    使用Kdiff3 来解决Git的文件冲突
    WordPress 用Windows Live Write写日志
    在GIT 中增加忽略文件夹与文件
    解决WinDbg下不能用 !ClrStack a
    DevExpress 实现下拉复选控件
    解决远程桌面连接后没有声音的问题
    读书
  • 原文地址:https://www.cnblogs.com/woooodlin/p/12926746.html
Copyright © 2011-2022 走看看