我的编程空间,编程开发者的网络收藏夹
学习永远不晚

并发编程之Java内存模型

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

并发编程之Java内存模型

简介:

Java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰Java程序员,这一系列几篇文章将揭开Java内存模型的神秘面纱。

这一系列的文章大致分4个部分,分别是:

  • Java内存模型基础,主要介绍内存模型相关基本概念
  • Java内存模型中的顺序一致性,主要介绍重排序与顺序一致性内存模型
  • 同步原语,主要介绍三个同步原语(synchronizedvolatile和final)的内存语义及重排序规则在处理器中的实现
  • Java内存模型的设计,主要介绍Java内存模型的设计原理,及其与处理器内存模型和顺序一致性内存模型的关系。

一、Java内存模型的基础

1.1 并发编程模型的两个关键问题

在并发编程中需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体)。

通信——线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。

  • 共享内存:线程之间共享程序的公共状态,通过读写内存中的公共转台进行隐式通信
  • 消息传递:线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信

同步——程序中用于控制不同线程键操作发生相对顺序的机制。

  • 共享内存:同步是显式进行的,由于程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行
  • 消息传递:同步是隐式进行的,由于消息的发送必须在消息的接收之前。

总结:

Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明,如果编写多线程程序的Java程序员不理解隐式进行线程之间的通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。

1.2 Java内存模型的抽象结构

Java中所有的实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享(文章中用“共享变量”指代)。局部变量(Local Variables)、方法定义参数(Formal Method Parameters)和异常处理器参数(Exception Handler Parameters)不会在线程之间共享,它们不会存在内存可见性问题,因此也不受内存模型的影响。
Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存时JMM的一个抽象概念,并不真实存在。JMM涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

图示:Java内存模型的抽象示意图

从上图来看,线程A和线程B之间要通信的话,必须经历下面2个步骤。

  • 线程A把本地内存A中更新过的变量刷新到主内存中
  • 线程B到主内存中去读取线程A之前已更新过的共享变量

图示:线程之间通信示意图

如上图所示,本地内存A和本地内存B有主内存中共享变量X的副本。假设初始时,这三个内存中的X的值都是0.线程A在执行时,把更新后的X的值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信是,线程A首先把自己本地内存中修改后的X刷新到主内存中,此时主内存中的X值变为了1.随后,线程B到主内存中去读取线程A更新后的X值,此时线程B的本地内存X的值也更新成了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。

1.3 从源代码到指令重排序

在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分为三种类型:

  • 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将对跳指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应及其指令的执行顺序。
  • 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

从Java源代码的最终实际执行的指令序列,会分别经历下面3种重排序,其中1属于编译器重排序,2和3属于处理器重排序。

源代码到最终执行的指令序列示意图:

重排序可能会导致多线程程序出现内存可见性问题,对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都需要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障(Memory Barries, Intel称之为Memory Fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序。
JMM属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保障。

1.4 写缓冲区和内存屏障

1.4.1 写缓冲区

现代处理器都会使用写缓冲区临时保存向内存中写入的数据。写缓冲区的主要作用:

  • 可以保证指令流水线持续运行,可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。
  • 它以批处理的方式方式刷新写缓冲区,以及合并写缓冲区中对统一地址的多次写,减少对内存总线的占用。

常见处理器允许的重排序类型(Y-表示允许两个操作重排序,N-表示处理器不允许两个操作重排序)

处理器 \规则 Load-Load Load-Store Store-Store Store-Load 数据依赖性
SPARC-TSO N N N Y N
x86 N N N Y N
IA64 Y Y Y Y N
PowerPC Y Y Y Y N
说明:常见处理器都允许Store-Load重排序;常见的处理器都不允许对存在数据依赖性的操作做重排序。N多的表示处理器拥有相对较强的处理器内存模型。
由于写缓冲器仅仅只对它所在的处理器可见,这个特性会对内存操作的执行顺序产生非常重要的影响:处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致。
举例说明:
示例项目 \处理器 Processor A Processor B
伪代码 a=1; //A1x=b;//A2 b=2;//B1y=a;//B2
可能运行结果 初始状态:a=b=0;处理器允许执行后得到结果:x=y=0;
假设处理器A和处理器B按程序的顺序并行执行内存访问,最终可能得到x=y=0的结果,具体原因如下:

处理器和内存交互:

说明:处理器A和处理器B可以同时把共享变量写入自己的写缓冲区(A1、B1),然后从内存中读取另一个共享变量(A2、B2),最后才把自己写缓冲区中保存的脏数据刷新到内存中(A3、B3)。当以这种时序执行时,程序就可以得到x=y=0结果。

1.4.2 内存屏障

为了保证内存可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。

JMM把内存屏障指令分为4类:

屏障类型 指令示例 说明
LoadLoad Barriers Load1;LoadLoad;Load2 确保Load1数据的装载先于Load2及所有后续装载指令的装载
StoreStore Barriers Store1;StoreStore;Store2 确保Store1数据对其他处理器可见(刷新到主内存)先于Store2及所有后续存储指令的存储
LoadStore Barriers Load1;LoadStore;Store2 确保Load1数据装载先于Store2及后续的存储指令刷新到内存
StoreLoad Barriers**** Store1;StoreLoad;Load2 确保Store1数据对其他处理器变得可见(指刷新到主内存)先于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行屏障之后的内存访问指令。

StoreLoad Barriers是一个“全能型屏障”,它同时具有其它3个屏障的效果。现代大多数处理器支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为处理器需要把缓冲区的内容全部刷新到内存中(Buffer Fully Flush)。

1.5 happens-before 简介

从JDK1.5开始,Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这里的两个操作可以是单线程也可以是多线程。

happens-before规则:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程的任意后续操作。
  • 监视器锁规则:对于一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对于一个volitale域的写,happens-before于任意后续对这个volatile域的读。
  • 传递性:如果A happens-before B,且B happens-before C ,那么A happens-before C

注意:

两个操作之间具有happens-before关系,并不意味着前一个操作必须在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visiable to and ordered beofre the second)。

图示happens-before与JMM的关系:

一个happens-before规则对应于一个或多个编译器个处理器重排序规则。对于Java程序员来说,happens-before规则简单易懂,它避免了Java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法。

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

并发编程之Java内存模型

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java并发编程中的内存模型是什么

这篇文章主要介绍“Java并发编程中的内存模型是什么”,在日常操作中,相信很多人在Java并发编程中的内存模型是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java并发编程中的内存模型是什么”的疑惑有所
2023-06-25

怎么浅谈Java并发编程中的Java内存模型

这篇文章的内容主要围绕怎么浅谈Java并发编程中的Java内存模型进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!物理计算机并发问题在介绍Java内存
2023-06-17

Java并发编程之volatile与JMM多线程内存模型实例分析

本篇内容主要讲解“Java并发编程之volatile与JMM多线程内存模型实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java并发编程之volatile与JMM多线程内存模型实例分析”
2023-06-30

Java并发中的内存模型

这篇文章主要讲解了“Java并发中的内存模型”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发中的内存模型”吧!CPU和内存在讲JMM之前,我想先和大家聊聊硬件层面的东西。大家应该都
2023-06-02

Java 内存模型与并发编程:揭示多线程编程背后的奥秘

Java 内存模型(JMM)为多线程编程提供了一套规则,用于定义共享内存中变量的可见性和原子性。了解 JMM 的工作原理对于理解和解决并发编程中的问题至关重要。
Java 内存模型与并发编程:揭示多线程编程背后的奥秘
2024-02-04

Java内存模型的并发处理示例

这篇文章给大家分享的是有关Java内存模型的并发处理示例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Java的优点是什么1. 简单,只需理解基本的概念,就可以编写适合于各种情况的应用程序;2. 面向对象;3.
2023-06-14

Golang并发编程之GMP模型详解

传统的并发编程模型是基于线程和共享内存的同步访问控制的,共享数据受锁的保护,线程将争夺这些锁以访问数据。本文将介绍Go并发编程中的GMP模型,感兴趣的可以了解一下
2023-03-22

java高并发的volatile与Java内存模型是什么

这篇文章将为大家详细讲解有关java高并发的volatile与Java内存模型是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。public class Demo09 { public stati
2023-06-25

Java 内存模型与死锁:深入理解并发编程中的死锁问题

本文深入探讨 Java 内存模型与死锁问题之间的关联,并以示例代码阐释死锁的成因和解决方法,旨在帮助读者深入理解并发编程中的死锁问题。
Java 内存模型与死锁:深入理解并发编程中的死锁问题
2024-02-04

Golang并发编程之GMP模型怎么实现

本文小编为大家详细介绍“Golang并发编程之GMP模型怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang并发编程之GMP模型怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。0. 简介传统
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录