我们一起学并发编程:Java内存模型三顺序一致性

1.1Java内存模型规范对数据竞争的定义如下 在一个线程中写一个变量 在另一个线程中读同一个变量 写和读没有通过同步来排序 如果一个多线程程序能够正确同步,这个程序将是一个没有数据竞争的程序,往往存在数据竞争的程序,运行结果与我们的预期结果都会存在偏差。1.2JMM对多线程程序的内存一致性做的保证 如果程序正确...
我们一起学并发编程:Java内存模型三顺序一致性
简介:
顺序一致性内存模型是一个理论参考模型,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照。
1、数据竞争和顺序一致性当程序未正确同步时,就可能存在数据竞争。
1.1Java内存模型规范对数据竞争的定义如下在一个线程中写一个变量
在另一个线程中读同一个变量
写和读没有通过同步来排序
如果一个多线程程序能够正确同步,这个程序将是一个没有数据竞争的程序,往往存在数据竞争的程序,运行结果与我们的预期结果都会存在偏差。
1.2JMM对多线程程序的内存一致性做的保证如果程序正确同步(正确使用synchronized、volatile和final),程序的执行将具有顺序一致性(SequentiallyConsistent)——即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。
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程序用锁来同步
packagecom.lizba.p1;/***<p>*同步示例*</p>**@Author:Liziba*@Date:2021/6/821:44*/publicclassSynReorderExample{//定义变量ainta=0;//flag变量是个标记,用来标志变量a是否被写入booleanflag=false;publicsynchronizedvoidwriter(){//获取锁a=1;flag=true;}//释放锁publicsynchronizedvoidreader(){//获取锁if(flag){inti=a*a;System.out.println("i:"+i);}}//释放锁}测试代码
/***测试**@paramargs*/publicstaticvoidmain(String[]args){finalSynReorderExamplere=newSynReorderExample();newThread(){publicvoidrun(){re.writer();}}.start();newThread(){publicvoidrun(){re.reader();}}.start();}执行多次结果结果都为1

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

总结
在顺序一致性模型中,所有操作完全按程序的顺序串行执行。而在JMM中,临界区内的代码可以重排序(但JMM不允许临界区的代码“逸出”到临界区之外,那样会破坏监视器锁的语义)。JMM会在进入临界区和退出临界区的关键时间点做一些特殊处理,使得线程在这两个时间点具有顺序一致性模型中相同的内存视图。虽然线程A在临界区内做了重排序,但由于监视锁互斥执行的特性,这里线程B无法“观察”到线程A在临界区内的重排序。JMM在具体实现上的基本方针为:在不改变(正确同步)程序执行结果的前提下,尽可能为编译器和处理器的优化打开方便大门。
2.4未同步程序的执行特性对于未同步或者未正确同步(代码写错了的兄弟们),JMM只提供最小的安全性:
线程执行时读取到的值不会无中生有(OutOfThinAir)
之前某个线程写入的值
默认值(0、Null、False)--JVM会在已经清零了内存空间(Pre-zeroedMemory)分配对象。
未同步程序在两个模型中的执行特性对比
比较内容\模型名称顺序一致性模型JMM模型单线程内顺序执行√×一致的操作执行顺序√×64位long型和double型变量写原子性√×第三个差异和总线的机制有关。在一些32位处理器上,处理64位的数据写操作,需要将一个写操作拆分为两个32位的写操作。
3、64位long型和double型变量写原子性3.1CPU、内存和总线简述在计算机中,数据通过总线在处理器和内存之间传递,每次处理器和内存之间的数据传递都是通过一系列的步骤来完成的,这一系列的步骤称之为总线事务(BusTransaction)。总线事务包括读事务(ReadTransaction)和写事务(WriteTransaction),事务会读\写内存中一个或多个物理上连续的字。
读事务→内存到处理器
写事务→处理器到内存
重点是,总线会同步试图并发使用总线的事务。在一个处理器执行总线事务期间,总线会禁止其他处理器和I\O设备执行内存的读\写。
图示:

总线工作机制
由上图所示,假设处理器A、B、C、D同时向总线发起总线事务,这时总线总裁(BusArbitration)会对竞争作出裁决,这里假设处理器A在竞争中获胜(总线仲裁会确保所有处理器能公平访问内存)。此时处理器A继续它的总线事务,而其他所有的总线事务必须要等待A的事务完成才能再次执行内存的读\写操作。总线事务工作机制确保处理器对内存的访问以串行的方式执行。在任意时间点都只有一个处理器可以访问内存,这个特性能确保总线事务之间的内存读\写操作具有原子性。
3.2long和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并发编程艺术》,下篇总结“volatile的内存语义”,敬请关注。
2024-09-19
mengvlog 阅读 12 次 更新于 2025-06-20 00:59:25 我来答关注问题0
  • 一、Java内存模型的基础1.1并发编程模型的两个关键问题 在并发编程中需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体)。 通信——线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。 共享内存:线程之间共享程序的公共状态,通过读...

  • 我们一起学并发编程:Java内存模型(三)顺序一致性简介:顺序一致性内存模型是一个理论参考模型,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照。1、数据竞争和顺序一致性当程序未正确同

  •  猪八戒网 我们一起学并发编程:Java内存模型(八)双重检查锁定与延迟初始化

    在Java语言规范中,首次发生如下情况中的任意一种,一个类或者一个接口类型T将会被立即初始化: T是一个类,而且一个T类型的实例被创建 T是一个类,且T中声明的一个静态方法被调用 T中声明的一个静态字段被赋值 T中声明的一个静态字段被使用,而且这个字段不是一个常量字段 T是一个顶级类(TopLevelClass),而且一...

  •  文暄生活科普 CAS底层原理示例+详解,终于弄清楚了!

    大家好,今天咱们一起探讨CAS(Compare-And-Swap)这个并发编程中的核心概念。CAS是一种CPU原语,主要作用是在内存中判断某个位置的值是否为预期值,若相符则进行更新,整个过程在硬件层面保证原子性,避免数据不一致,从而实现线程安全。在Java中,sun.misc.Unsafe类提供了实现CAS的接口,JVM会通过底层的...

  •  ya...0@163.com java学习路线

    Java学习路线一般有以下几个阶段:第一阶段,JavaSE基础:Java环境搭建、Java流程控制语句-for循环、switch选择判断、循环嵌套、数组拷贝等。第二阶段,JavaWeb:MySQL安装、管理、创建数据库、MySQLUPDATE查询、Mysql高级操作等。第三阶段,Java高级框架-SSH:Struts2异常处理、Struts2+Log4j集成、Struts2和...

檬味博客在线解答立即免费咨询

Java相关话题

Copyright © 2023 WWW.MENGVLOG.COM - 檬味博客
返回顶部