Java并发11:Java内存模型、指令重排、内存屏障、happens-before原则

时间:2021-08-17 23:26:12   收藏:0   阅读:74

本章主要对Java并发中非常重要的概念Java内存模型、指令重排和happens-before原则进行学习。

1.内存模型

如果想要设计表现良好的并发程序,理解Java内存模型是非常重要的。

Java线程之间的通信Java内存模型(Java Memory Model,简称JMM)控制
JMM决定一个线程对共享变量的写入何时对另一个线程可见

JMM把JVM内部划分为线程栈(Thread stack)和堆(Heap),这张图演示了JMM的逻辑视图:
技术分享图片

 

说明:

  1. 每个线程都拥有自己的线程栈。
  2. 线程栈包含了当前线程执行的方法调用相关信息,我们也把它称作调用栈。
  3. 线程栈还包含了当前方法的所有本地变量信息。
  4. 一个线程只能读取自己的线程栈,每个线程中的本地变量都会有自己的版本,线程中的本地变量对其它线程是不可见的。
  5. 所有原始类型(8种)的本地变量都直接保存在线程栈当中,线程可以传递原始类型的副本给另一个线程,线程之间无法共享原始类型的本地变量。
  6. 堆区包含了Java应用创建的所有对象信息。
  7. 堆区包含了所有线程创建的对象信息。
  8. 堆区包含了原始类型的封装类(如Byte、Integer、Long等等)的对象信息。

下图展示了调用栈和本地变量都存储在栈区,对象都存储在堆区:

技术分享图片

 

 

 

 

堆中的对象可以被多线程共享:

一个线程如果要使用一个共享变量,则首先需要从Heap中加载这个共享变量,并在Thread Stack中形成这个共享变量的副本。
一个线程使用完这个共享变量之后,还需要将Thread Stack中共享变量的副本更新到Heap中。
竞争:

如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。

举例:
Obj.count的初始值是0,线程A和线程B同时对Obj.count做自增操作。

串行情况之一:

并行情况之一:

 

经过学习JMM,可以加深对并发会安全问题的理解。

2.指令重排

CPU执行单元的速度要远超主存访问速度。

在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。

2.1.单线程程序语义

编译器、runtime和处理器都必须遵守as-if-serial语义。

不管怎么重排序,单线程的执行结果不会改变。

2.2.数据依赖性

如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。

编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。

2.3.内存屏障

内存屏障或内存栅栏(Memory Barrier),是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。

内存屏障有两个能力:

  1. 就像一套栅栏分割前后的代码,阻止栅栏前后的没有数据依赖性的代码进行指令重排序,保证程序在一定程度上的有序性。
  2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效,保证数据的可见性。


内存屏障有三种类型和一种伪类型:

  1. lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。
  2. sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见。
  3. mfence,即全能屏障,具备ifence和sfence的能力。
  4. Lock前缀:Lock不是一种内存屏障,但是它能完成类似全能型内存屏障的功能。

在Java中:

实现了内存屏障的技术有volatile。

volatile就是用Lock前缀方式的内存屏障伪类型来实现的。

学习指令重排,让我们明白,因为指令重排,多线程开发中,是存在很多有序性和可见性问题的。

3.happens-before原则

从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。

happens-before定义:

Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.

 

如果一个操作 happens-before 第二个操作,则第一个操作对第二个操作是可见的,并且一定发生在第二个操作之前。

重要的happens-before原则:

理解如下:

参考文献
[1] 聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障
[2] 内存屏障或内存栅栏【转】
[3] 全面理解Java内存模型
[4] happens-before俗解
[5] Chapter 17. Threads and Locks

 

原文:https://www.cnblogs.com/yaochunhui/p/15154117.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!