并发编程之Java内存模型顺序一致性

目录
  • 1、数据竞争和顺序一致性
    • 1.1 Java内存模型规范对数据竞争的定义
    • 1.2 JMM对多线程程序的内存一致性做的保证
  • 2、顺序一致性内存模型
    • 2.1 特性
    • 2.2 举例说明顺序一致性模型
    • 2.3 同步程序的顺序一致性效果
    • 2.4 未同步程序的执行特性
  • 3、 64位long型和double型变量写原子性
    • 3.1 CPU、内存和总线简述
    • 3.2 long和double类型的操作

简介:

顺序一致性内存模型是一个理论参考模型,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照。

1、数据竞争和顺序一致性

当程序未正确同步时,就可能存在数据竞争。

1.1 Java内存模型规范对数据竞争的定义

定义如下:

  • 在一个线程中写一个变量
  • 在另一个线程中读同一个变量
  • 写和读没有通过同步来排序

如果一个多线程程序能够正确同步,这个程序将是一个没有数据竞争的程序,往往存在数据竞争的程序,运行结果与我们的预期结果都会存在偏差。

1.2 JMM对多线程程序的内存一致性做的保证

如果程序正确同步(正确使用synchronizedvolatilefinal),程序的执行将具有顺序一致性(Sequentially Consistent)——即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。

2、顺序一致性内存模型

2.1 特性

  • 一个线程中的所有操作必须按照程序的执行顺序来执行
  • (不管是否正确同步)所有的线程都只能看到一个单一的操作执行顺序,每个操作都必须原子执行且立刻对所有线程可见。

图示:顺序一致性内存模型视图

在概念上,顺序一致性模型有一个单一的全局内存,这个内存通过一个左右摆动的开关可以连接到任意一个线程,同时每一个线程必须按照程序的顺序来执行内存的读/写操作。上图中可以看出,在任意时刻最多只有一个线程可以连接到内存。因此,在多线程并发执行时,图中的开关装置能把所有的内存读/写操作串行化(即在顺序一致性模型中所有操作之间具有全序关系)。

2.2 举例说明顺序一致性模型

假设两个线程A和B并发执行。其中

A线程的操作在程序中的顺序为:A1 - A2 - A3

B线程的操作在程序中的顺序为:B1 - B2 - B3

假设线程A和线程B使用监视器锁来正确同步,A线程的3个操作执行后释放监视器锁,随后B线程获取同一个监视器锁。那么程序在顺序一致性模型中的执行效果如下所示:顺序一致性模型的一种执行效果

假设线程A和线程B没有做同步,那么这个未同步的程序在顺序一致性模型中的另一种可能的效果如下所示:

顺序一致性模型的另一种执行效果:

未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但是所有线程都只能看到一个一直的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:A1 - B1 - A2 - B2 - A3 - B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。

但是,在JMM中就没有这个保证。未同步程序在JMM中不但整体的执行顺序是无序的,而且所有线程看到的操作执行顺序也可能不一致。 比如,在当前线程把写过的数据缓存在本地内存中,在没有刷新到主内存之前,这个写操作仅对当前线程可见;从其他线程的角度来观察,会认为这个写操作根本被当前线程执行。只有当前线程把本地内存中写过的数据刷新到主内存之后,这个写操作才能对其他线程可见。这种情况就会出现多种运行结果。

2.3 同步程序的顺序一致性效果

对上一章的ReorderExample程序用锁来同步

package com.lizba.p1;

/**
 * <p>
 *      同步示例
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/8 21:44
 */
public class SynReorderExample {

    // 定义变量a
    int a = 0;
    // flag变量是个标记,用来标志变量a是否被写入
    boolean flag = false;

    public synchronized void writer() {     // 获取锁
        a = 1;
        flag = true;
    }                                       // 释放锁

    public synchronized void reader() {     // 获取锁
        if (flag) {
            int i = a * a;
            System.out.println("i:" + i);
        }
    }                                       // 释放锁

}

测试代码

/**
  * 测试
  *
  * @param args
  */
public static void main(String[] args) {

    final SynReorderExample re = new SynReorderExample();

    new Thread() {
        public void run() {
            re.writer();
        }
    }.start();

    new Thread() {
        public void run() {
            re.reader();
        }
    }.start();
}

执行多次结果结果都为1

总结:

在上面的示例代码中,假设A线程执行writer()方法后,B线程执行reader()方法。这是一个正确同步的多线程程序。根据JMM规范,该程序的执行结果将与该程序在顺序一致性内存模型中的执行结果相同。

顺序一致性模型中和JMM内存模型中的执行时序图

总结

在顺序一致性模型中,所有操作完全按程序的顺序串行执行。而在JMM中,临界区内的代码可以重排序(但JMM不允许临界区的代码“逸出”到临界区之外,那样会破坏监视器锁的语义)。JMM会在进入临界区和退出临界区的关键时间点做一些特殊处理,使得线程在这两个时间点具有顺序一致性模型中相同的内存视图。虽然线程A在临界区内做了重排序,但由于监视锁互斥执行的特性,这里线程B无法“观察”到线程A在临界区内的重排序。JMM在具体实现上的基本方针为:在不改变(正确同步)程序执行结果的前提下,尽可能为编译器和处理器的优化打开方便大门。

2.4 未同步程序的执行特性

对于未同步或者未正确同步(代码写错了的兄弟们),JMM只提供最小的安全性:

线程执行时读取到的值不会无中生有(Out Of Thin Air)

  • 之前某个线程写入的值
  • 默认值(0、Null、False)-- JVM会在已经清零了内存空间(Pre-zeroed Memory)分配对象。

未同步程序在两个模型中的执行特性对比

比较内容\模型名称 顺序一致性模型 JMM模型
单线程内顺序执行 ×
一致的操作执行顺序 ×
64位long型和double型变量写原子性 ×

第三个差异和总线的机制有关。在一些32位处理器上,处理64位的数据写操作,需要将一个写操作拆分为两个32位的写操作。

3、 64位long型和double型变量写原子性

3.1 CPU、内存和总线简述

在计算机中,数据通过总线在处理器和内存之间传递,每次处理器和内存之间的数据传递都是通过一系列的步骤来完成的,这一系列的步骤称之为总线事务(Bus Transaction)。总线事务包括读事务(Read Transaction)和写事务(WriteTransaction),事务会读\写内存中一个或多个物理上连续的字。

  • 读事务 → 内存到处理器
  • 写事务 → 处理器到内存

重点:总线会同步试图并发使用总线的事务。在一个处理器执行总线事务期间,总线会禁止其他处理器和I\O设备执行内存的读\写。

图示:总线工作机制

由上图所示:设处理器A、B、C、D同时向总线发起总线事务,这时总线总裁(Bus Arbitration)会对竞争作出裁决,这里假设处理器A在竞争中获胜(总线仲裁会确保所有处理器能公平访问内存)。此时处理器A继续它的总线事务,而其他所有的总线事务必须要等待A的事务完成才能再次执行内存的读\写操作。总线事务工作机制确保处理器对内存的访问以串行的方式执行。在任意时间点都只有一个处理器可以访问内存,这个特性能确保总线事务之间的内存读\写操作具有原子性。

3.2 long和double类型的操作

在一些32位的处理器上,如果要求对64位数据的写操作具有原子性,那么会有非常大的同步开销。Java语言规范中鼓励但不强求JVM对64位long型和double类型的变量写操作具有原子性。当JVM在这种处理器上运行时,会把一个64位的变量写操作拆成两个32位写操作来执行,此时写不具备原子性。

图示:总线事务执行的时序图

存在问题:

假设处理器A写一个long类型的变量,同时处理器B要读这个long类型的变量。处理器A中64位的写操作被拆分成两个32位的写操作,且这两个32位的写操作被分配到不同的事务中执行。此时,处理器B中64位的读操作被分配到单个读事务中执行。如果按照上面的执行顺序,那么处理器B读取的将会是一个不完整的无效值。

处理方式:

JSR-133内存模型开始(JDK1.5),写操作能拆分成两个32位写事务执行,读操作必须在单个事务中执行。

到此这篇关于并发编程之Java内存模型顺序一致性的文章就介绍到这了,更多相关Java内存模型顺序一致性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java并发内存模型详情

    目录 1.Java内存模型 2.硬件内存架构 3.实际执行 3.1 共享对象可见性 3.2 竞争条件 Java是一门支持多线程执行的语言,要编写正确的并发程序,了解Java内存模型是重要前提.而了解硬件内存模型有助于理解程序的执行. 本文主要整理以下内容 Java内存模型 硬件内存架构 共享对象可见性 竞争条件 1.Java内存模型 Java内存模型最新修订是在Java5. JSR-176 罗列了 J2SE5.0 相关发布特性,包含其中的 JSR-133(JavaTM内存模型与线程规范),jav

  • 浅谈JAVA Actor模型的一致性与隔离性

    一.Actor模型介绍 在单核 CPU 发展已经达到一个瓶颈的今天,要增加硬件的速度更多的是增加 CPU 核的数目.而针对这种情况,要使我们的程序运行效率提高,那么也应该从并发方面入手.传统的多线程方法又极其容易出现 Bug 而难以维护,不过别担心,今天将要介绍另一种并发的模式能一定程度解决这些问题,那就是 Actor 模型. Actor 模型其实就是定义一组规则,这些规则规定了一组系统中各个模块如何交互及回应.在一个 Actor 系统中,Actor 是最小的单元模块,系统由多个 Actor 组

  • java内存模型jvm虚拟机简要分析

    目录 主内存和工作内存 内存间的交互操作 原子性.可见性.有序性 原子性 可见性 有序性 主内存和工作内存 Java 内存模型规定了所有的变量都存储在主内存中, 每条线程有自己的工作内存 线程的工作内存中保存了被该线程使用的变量的主内存副本, 线程对变量的所有操作 (读取.赋值等) 都必须在工作内存中进行, 而不能直接读写主内存中的数据 不同的线程之间也无法直接访问对方工作内存中的变量, 线程间变量值的传递均需要通过主内存来完成 内存间的交互操作 原子性.可见性.有序性 Java 内存模型是围绕

  • Java业务中台确保数据一致性的解决方案

    目录 引言 数据一致性原理预备知识 1.本地事务 2.分布式事务 (1)一个事务中包含了多数据库操作 (2)一个事务中包含了多服务访问同一数据库 (3)一个事务包含了多个微服务调用数据不一致引发的问题 数据一致性解决方案 1.刚性事务 2.柔性事务 (1)TCC 模式 (2)可靠消息最终一致性 总结 引言 随着业务的发展,微服务架构逐渐成为当下业务中台的主流架构形式,它不但解决了各个应用之间的解耦问题,同时也解决了单体应用的性能问题实现可扩展可动态伸缩的能力.如下图所示,业务中台就是将平台的通用

  • Java并发编程之内存模型

    目录 一.Java内存模型的基础 1.1 并发编程模型的两个关键问题 1.2 Java内存模型的抽象结构 1.3 从源代码到指令重排序 1.4 写缓冲区和内存屏障 1.4.1 写缓冲区 1.4.2 内存屏障 1.5 happens-before 简介 简介: Java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰Java程序员,这一系列几篇文章将揭开Java内存模型的神秘面纱. 这一系列的文章大致分4个部分,分别是: Java内存模型基础,主要介绍内存模型相关基本概念 Java内存模型

  • java高并发的volatile与Java内存模型详解

    public class Demo09 { public static boolean flag = true; public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println("线程" + this.getName() + " in"); while (flag) { ;

  • 并发编程之Java内存模型顺序一致性

    目录 1.数据竞争和顺序一致性 1.1 Java内存模型规范对数据竞争的定义 1.2 JMM对多线程程序的内存一致性做的保证 2.顺序一致性内存模型 2.1 特性 2.2 举例说明顺序一致性模型 2.3 同步程序的顺序一致性效果 2.4 未同步程序的执行特性 3. 64位long型和double型变量写原子性 3.1 CPU.内存和总线简述 3.2 long和double类型的操作 简介: 顺序一致性内存模型是一个理论参考模型,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照

  • 并发编程之Java内存模型锁的内存语义

    目录 1.锁的释放-获取建立的happens-before关系 2.锁释放和获取的内存语义 3.锁内存的语义实现 4.concurrent包的实现 简介: 锁的作用是让临界区互斥执行.本文阐述所得另一个重要知识点--锁的内存语义. 1.锁的释放-获取建立的happens-before关系 锁是Java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息. 锁释放-获取的示例代码: package com.lizba.p1; /** * <p>

  • 并发编程之Java内存模型volatile的内存语义

    1.volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对单个读/写操作做了同步. 代码示例: package com.lizba.p1; /** * <p> * volatile示例 * </p> * * @Author: Liziba * @Date: 2021/6/9 21:34 */ public class VolatileFeatureExample { /** 使用volatile声明64位的long型

  • Java并发编程之Java内存模型

    目录 1.什么是Java的内存模型 2.为什么需要Java内存模型 3.Java内存模型及操作规范 4.Java内存模型规定的原子操作 5.Java内存模型同步协议 6.Java内存模型的HB法则 JMM的HB法则 总结 1.什么是Java的内存模型 Java内存模型简称JMM(Java Memory Model),JMM是和多线程并发相关的一组规范.各个jvm实现都要遵循这个JMM规范.才能保证Java代码在不同虚拟机顺利运行.因此,JMM 与处理器.缓存.并发.编译器有关.它解决了CPU 多

  • 并发编程之Java内存模型

    目录 一.Java内存模型的基础 1.1 并发编程模型的两个关键问题 1.2 Java内存模型的抽象结构 1.3 从源代码到指令重排序 1.4 写缓冲区和内存屏障 1.4.1 写缓冲区 1.4.2 内存屏障 1.5 happens-before 简介 简介: Java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰Java程序员,这一系列几篇文章将揭开Java内存模型的神秘面纱. 这一系列的文章大致分4个部分,分别是: Java内存模型基础,主要介绍内存模型相关基本概念 Java内存模型

  • Java 高并发三:Java内存模型和线程安全详解

    网上很多资料在描述Java内存模型的时候,都会介绍有一个主存,然后每个工作线程有自己的工作内存.数据在主存中会有一份,在工作内存中也有一份.工作内存和主存之间会有各种原子操作去进行同步. 下图来源于这篇Blog 但是由于Java版本的不断演变,内存模型也进行了改变.本文只讲述Java内存模型的一些特性,无论是新的内存模型还是旧的内存模型,在明白了这些特性以后,看起来也会更加清晰. 1. 原子性 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰

  • Java内存模型final的内存语义

    目录 1.final域的重排序规则final 2.写final域的重排序规则 3.读final与的重排序规则 4.final域为引用类型 5.为什么final引用不能从构造函数内"逸出" 6.final语义在处理器中的实现 7.JSR-133为什么要增强final的语义 上篇并发编程之Java内存模型volatile的内存语义介绍了volatile的内存语义,本文讲述的是final的内存语义,相比之下,final域的读和写更像是普通变量的访问. 1.final域的重排序规则final

  • Java 内存模型(JMM)

    目录 四.Happens-Before 规则 Java 内存模型 一.什么是 Java 内存模型 Java 内存模型定义如下: 内存模型限制的是共享变量,也就是存储在堆内存中的变量,在 Java 语言中,所有的实例变量.静态变量和数组元素都存储在堆内存之中.而方法参数.异常处理参数这些局部变量存储在方法栈帧之中,因此不会在线程之间共享,不会受到内存模型影响,也不存在内存可见性问题. 通常,在线程之间的通讯方式有共享内存和消息传递两种,很明显,Java 采用的是第一种即共享的内存模型,在共享的内存

  • Java 内存模型(JVM)

    目录 前言 一.什么是 Java 内存模型 二.为什么需要 Java 内存模型 三.顺序一致性内存模型 四.Happens-Before 规则 前言 在并发编程中,当多个线程同时访问同一个共享的可变变量时,会产生不确定的结果,所以要编写线程安全的代码,其本质上是对这些可变的共享变量的访问操作进行管理.导致这种不确定结果的原因就是可见性.有序性和原子性问题,Java 为解决可见性和有序性问题引入了 Java 内存模型,使用互斥方案(其核心实现技术是锁)来解决原子性问题.这篇先来看看解决可见性.有序

  • Java并发编程之volatile与JMM多线程内存模型

    目录 一.通过程序看现象 二.为什么会产生这种现象(JMM模型)? 三.MESI 缓存一致性协议 一.通过程序看现象 在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码.这段代码的逻辑很简单:主线程启动了两个子线程,一个线程1.一个线程2.线程1先执行,sleep睡眠2秒钟之后线程2执行.两个线程使用到了一个共享变量shareFlag,初始值为false.如果shareFlag一直等于false,线程1将一直处于死循环状态,所以我们在线程2中将shareFlag设置为true

随机推荐