zoukankan      html  css  js  c++  java
  • 并发编程-Java内存模型

    将之前看过的关于并发编程的东西总结记录一下,本文简单记录Java内存模型的相关知识。

    1. 并发编程两个关键问题

    并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步。

    (1)在命令式编程(命令式编程侧重于告诉计算机先做什么后做什么,与声明式只告诉做什么,不告诉怎么做不同)中,线程间的通信机制有两种:共享内存和消息传递。

      ① 在共享内存的并发模型中,线程之间共享程序的公共状态,通过读写内存的公共状态进行隐式通信;

      ② 在消息传递的并发模型中,线程间没有公共状态,其通过发生消息进行显式通信。

    (2)线程同步是用于控制不同线程间操作发生的相对顺序,在共享内存并发模型中,同步是显式进行的,如通过synchronized控制线程互斥的访问某方法或代码块;在消息传递的并发模型中,消息发送再接收之前,同步是隐式进行的。

    Java的并发采用的是共享内存模型,通信是隐式的,同步是显式的。

    2. Java内存模型

    (1)计算机硬件结构

      介绍Java内存模型之前,先看下目前计算机硬件架构,如下图(摘自Java内存模型,具体介绍可参考该文章)。

      由于计算机的存储设备与处理器的运算速度几个数量级的差距,所以现代计算机系统都引入一层读写速度与处理器接近的高速缓存(CPU Cache)作为内存与处理器之间的缓冲。将运算需要使用的数据复制到缓存中,加快运算速度,当运算结束后再从缓存同步到内存中,这样处理器不需要等待缓慢的内存IO。

       在此情况下,引入了新的问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存,它们共享同意主内存(Main Memory)。为解决缓存一致性的问题,需要各处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,如MSI协议(监听一致性协议通过利用总线将处理器核的私有Cache 和主存储器连接在一起,每一个私有 Cache 中的控制器会时刻侦听总线上的消息,并根据消息的类型做出相应的操作---百度百科)等。

    (2)Java 内存模型的抽象结构

      Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将共享变量存储到内存和从内存取出的机制,这些共享变量包括实例字段、静态字段和构成数组对象的元素,可以认为都存储在堆内存中。

      Java内存模型的抽象结构如下(摘自Java内存模型的深入理解,该图也是《Java并发编程的艺术》中的):

      ① 该模型与上边计算机硬件架构类似,但该主内存是虚拟机的一部分,主要存储所有变量(主要包含堆区部分的变量);

      ② 每条线程都有自己的本地内存(或者叫工作内存),保存主内存汇总变量的副本,线程对变量的操作都是在该内存中进行,可以与处理器高速缓存类比;

      ③ 不同线程间的通信只能通过主内存进行。

    (3)内存间的交互

      一个变量从主内存拷贝到本地内存,或从本地内存同步到主内存,需要通过具体的交互协议。Java内存模型定义了8种操作来完成。

    lock(锁定) 主内存,将主内存中变量标识为一条线程独占状态
    unlock(解锁) 主内存,与lock相反,释放变量的锁
    read(读取) 主内存,将变量从主内存传输到本地内存,以便后续的load操作
    load(载入) 本地内存,将变量放入本地内存的变量副本中
    use(使用) 本地内存,将该变量的值传给执行引擎,用于执行操作,对应使用到该变量的字节码指令
    assign(赋值) 本地内存,将从执行引擎接收到的值赋给本地内存的变量,对应赋值字节码指令
    store(存储) 本地内存,将本地内存中的变量值传送到主内存,以便后续的write操作
    write(写入) 主内存,将从store操作中获取的变量值放入主内存的变量中

      如果要把一个变量从主内存复制到本地内存,需要顺序执行read和load操作,如果把变量从本地内存同步会主内存,需要顺序执行store和write操作。Java内存模型只要求上边两个顺序执行,并不能保证连续执行,即他们之间是可以插入其他命令,非原子操作。这8种基本操作必须满足以下规则:

      ① read和load、store和write不能单独出现

      ② 不允许一个线程丢弃最近assign操作,即本地内存中的改变必须同步回主内存;

      ③ 一个新共享变量只能在主内存中诞生(线程中本地变量可以)

      ④ 一个变量同一时刻只允许一个线程进行lock操作,同一线程可以多次执行lock(对应同一线程可以多次执行synchronized)

      ⑤ 对一个变量执行lock操作时,将会清空本地内存中该变量的值,再执行引擎使用该变量时,需要重新执行load或assign操作初始化变量的值

      ⑥ 对变量执行lock操作,就不可以执行unlock操作

      ⑦ 将一个变量unlock之前,必须先把该变量同步回主内存(执行store和write操作)

    (4)原子性、可见性和有序性

      Java内存模型具有原子性、可见性和有序性等3个特性。

      ① 原子性,上边描述的8种操作都具有原子性,如需要更大范围的原子性,可以使用lock和unlock操作。虽然该指令并没有开放给用户使用,但提高了更高层次的字节码指令monitorenter和monitorexit,其对应Java代码中锁定代码块的synchronized关键字。

      ② 可见性,当一个线程对共享变量修改后,其他线程可以立即得到该值。Java内存模型是通过将修改后的变量值同步回主内存,在线程读取前从主内存刷新该变量新值实现可见性,对普通变量和volatile变量都是如此,只是volatile变量的特殊规则保证新值能立即同步到主内存,每次使用前立即从主内存刷新,故volatile能保证多线程操作时变量的可见性,具体volatile讲述见后续文章。

      除了volatile之外,还有两个关键字能实现可见性,synchronized和final,前者通过对线程顺序执行实现,final修饰的字段在构造器中初始化后,并且构造器没有this引用逃逸(把this引用传递出去),其他线程就可以看到final字段的值。

      ③ 有序性,如果在本线程内观察,所有操作都是有序的,即无论编译器或处理器对指令怎么进行重排序,执行结果不变,遵从as-if-serial语义;如果一个线程观察另一个线程,所有操作都是无序的,是指存在“指令重排序”和“本地内存与主内存同步延迟”的现象。

      Java提供了volatile和synchronized保证线程之间的有序性,前者禁止指令重排序,后者由上边规则4获得。

    3. 总结

    1. Java线程间通信是通过共享内存的方式进行的,通信是隐式地通过更新主内存实现;线程间同步通过显式地通过互斥等方式实现

    2. 计算机硬件架构中引入高速缓存以加速处理器,通过相关协议解决多处理器中“缓存一致性”的问题

    3. Java内存模型与家算计硬件架构类似,主内存中存储共享变量,本地内存或工作内存中存储变量拷贝,用于各线程计算,可以通过锁实现同步

    4. 提供8种操作用于主内存与本地内存的交互,并且必须遵循一定的规则使用

    5. Java内存弄下具有原子性、可见性和有序性。

    6. 另外,Java内存模型中还存在“先行发生”(happen-before)的原则,其是内存模型中定义的两项操作之间的偏序关系,如说A先行发生于B,则A操作的结果B可以观察到。如Monitor Lock Rule、volatile变量写操作先行于读操作、线程终结、对象终结等。是判断数据是否存在竞争、线程是否安全的主要依据。

    4.参考

    《Java并发编程的艺术》

    《深入理解Java虚拟机》

  • 相关阅读:
    jQuery prop方法
    ftp550权限问题
    一个很适合用来套用后台的框架
    Ajax中post方法400和404的问题
    图片上传,直接通过js预览
    JavaScript的DOM扩展
    DevExpress v16.1.5图表、Dashboard等多个控件API发生变化
    MyEclipse 2016正式版更新内容
    Smart Tag——DevExpress WPF初探
    MyEclipse使用心得:集成和使用Maven的方法
  • 原文地址:https://www.cnblogs.com/shuimuzhushui/p/11335450.html
Copyright © 2011-2022 走看看