热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java内存模型浅析_浅析Java内存模型

在并发编程中,需要处理两个关键问题:线程之间如何通信以及线程之间如何同步。通信是指线程之间以何种机制来交换信息。同步是指程序中用于控制不同线程间操作发生

在并发编程中,需要处理两个关键问题:线程之间如何通信以及线程之间如何同步。通信是指线程之间以何种机制来交换信息。同步是指程序中用于控制不同线程间操作发生相对顺序的机制。在命令式编程中,线程之间的通信有两种:共享内存和消息传递。

Java的并发采用的是共享内存模型,在共享内存模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。而同步是显示进行的,为了正确的使用同步,防止内存可见性问题,我们需要了解线程间的隐式通信机制,因此我们需要了解Java内存模型。

Java内存模型

Java虚拟机规范定义了一种抽象的Java内存模型(本文简称为JMM),JMM的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节,这里的变量是指线程间共享变量,包括实例域、静态域和数组元素。局部变量、方法定义参数和异常处理器参数不会在线程间共享,因此也就不会有内存可见性问题。

从抽象角度看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存(也可称为工作空间),本地内存存储了该线程以读/写共享变量的副本。下图展示了Java内存模型的抽象结构示意图:

33671281496dc8c4cf1109d810363cd2.png

工作方式:

线程修改私有数据直接在工作空间中修改

线程修改共享数据,需要先将共享数据复制到工作空间中,然后在工作空间修改,修改完成后再刷新回主内存。

JMM通过控制主内存与每个线程的私有工作空间的交互,来为Java程序员提供内存可见性保证。

JMM与重排序

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。分为三种:

1)编译器优化: 编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

2)指令级并行的重排序: 现代处理器采用了指令级并行技术(Instrution-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

3)内存系统的重排序: 由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行的。

这些重排序可能会导致多线程程序出现内存可见性问题。对于编译器重排序,JMM的编译器重排序规则会禁止特定类型的编译器重排序。对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障(Memory Barriers)指令,通过内存屏障指令来禁止特定类型的处理器重排序。

as-if-serial语义

不管怎么重排序,单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

JMM与顺序一致性

顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证。顺序一致性内存模型有两大特征:

一个线程中的所有操作必须按照程序的顺序来执行

所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须是原子执行且立刻对所有线程可见。

JMM和处理器内存模型在设计时通常会以顺序一致性模型为参照,但是会做一些放松,也就是说相比顺序一致性内存模型会弱些,这样做是为了给处理器和和编译器留下足够的优化空间。下面来看下JMM对内存可见性的保证:

单线程程序: 编译器、runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。

正确同步的多线程程序: 如果程序是正确同步的,程序的执行将具有顺序一致性,即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。JMM通过限制编译器和处理器的重排序来保证。

未同步/为正确同步的多线程程序: JMM提供最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)。

JMM对并发特征的保证

JMM是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的:

原子性

原子性是指一个操作是不可中断的,要么全部执行成功,要么全部执行失败。在Java中基本数据类型的访问读写都是具备原子性的,而多个原子性的操作合并在一起是没有原子性的。

解决方式: synchronized、JUC中的lock

可见性

可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

解决方式: volatile、synchronized、final、JUC中的lock

有序性

有序性是指程序执行的顺序按照代码的先后顺序执行

解决方式: volatile、synchronized

JMM与happens-before规则

happens-before的概念最初由Leslie Lamport在一篇影响深远的论文(《Time,Clocks and the Ordering of Events in a Distributed system》)中提出。Leslie Lamport使用happens-before来定义分布式系统中事件之间的偏序关系。

从JDK5开始,Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。这两个操作可以在一个线程之内,也可以在不同线程之间。JMM通过happens-before关系向程序员提供跨线程的内存可见性保证。

《JSR-133: Java Memory Model and Thread Specification》对happens-before关系的定义如下:

1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

2)两个操作之间存在happens-before关系,并不意味着java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序后的执行结果,与按happens-before关系来执行的结果一致,那么JMM允许这种重排序。

上面的1)是JMM对程序员的承诺,上面的2)是JMM对编译器和处理器重排序的约束规则。

JMM的设计意图

JSR-133专家在设计JMM时的核心目标就是找到一个平衡点:一方面,要为程序员提供足够强的内存可见性保证;另一方面,对编译器和处理器的限制要尽可能地发送。下图展示了JMM的设计示意图:

7964b209a2e17a63c494e6a46d717ef1.png

从上图中我们可以看出两点:

JMM向程序员提供了足够强的内存可见性保证

JMM所遵循的一个基本原则:只要不改变程序的执行结果,编译器和处理器怎么优化都行。

happens-before规则

程序顺序规则: 一个线程中的每个操作,happens-before于该线程中的任意后续操作。

监视器锁规则: 对一个锁的的解锁,happens-before于随后对这个锁的加锁。

volatile变量规则: 对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

传递性: 如果A happens-before B,且B happens-before C,那么A happens-before C。

start()规则: 如果线程A执行操作ThreadB.start()(启动线程B)并成功返回,那么线程B中的任意操作happens-before于线程A中的任意操作。

join()规则: 如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

happens-before规则是判断数据是否存在竞争,线程是否安全的主要依据。 如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。如果两个操作之间的关系不在上面的所列,并且无法从规则中推导出来的话,那么它们就没有顺序性保障,虚拟机可以对它们进行随意的重排序。

参考资料

Java并发编程的艺术 方腾飞 魏鹏 程晓明 著

Java并发编程实战 童云兰 译

------------本文结束感谢您的阅读------------



推荐阅读
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 本文介绍了Tomcat的基本操作,包括启动、关闭及首次访问的方法,并详细讲解了如何在IDEA中创建Web项目,配置Servlet及其映射,以及如何将项目部署到Tomcat。 ... [详细]
  • SSE图像算法优化系列三:超高速导向滤波实现过程纪要(欢迎挑战)
    自从何凯明提出导向滤波后,因为其算法的简单性和有效性,该算法得到了广泛的应用,以至于新版的matlab都将其作为标准自带的函数之一了&#x ... [详细]
  • 本文详细介绍了如何使用C#实现不同类型的系统服务账户(如Windows服务、计划任务和IIS应用池)的密码重置方法。 ... [详细]
  • 本文探讨了如何通过Service Locator模式来简化和优化在B/S架构中的服务命名访问,特别是对于需要频繁访问的服务,如JNDI和XMLNS。该模式通过缓存机制减少了重复查找的成本,并提供了对多种服务的统一访问接口。 ... [详细]
  • 本文分享了作者在使用LaTeX过程中的几点心得,涵盖了从文档编辑、代码高亮、图形绘制到3D模型展示等多个方面的内容。适合希望深入了解LaTeX高级功能的用户。 ... [详细]
  • 本文将详细介绍Fuel CMS如何基于CodeIgniter框架构建,包括其单入口模式的实现方式及关键配置文件的作用。通过分析本地环境中的index.php和.htaccess文件,我们将更好地理解Fuel CMS的核心架构。 ... [详细]
  • 【MySQL】frm文件解析
    官网说明:http:dev.mysql.comdocinternalsenfrm-file-format.htmlfrm是MySQL表结构定义文件,通常frm文件是不会损坏的,但是如果 ... [详细]
  • 本文详细介绍了如何在 Ubuntu 14.04 系统上搭建仅使用 CPU 的 Caffe 深度学习框架,包括环境准备、依赖安装及编译过程。 ... [详细]
  • 本文探讨了Python类型注解使用率低下的原因,主要归结于历史背景和投资回报率(ROI)的考量。文章不仅分析了类型注解的实际效用,还回顾了Python类型注解的发展历程。 ... [详细]
  • 在使用 Nginx 作为服务器时,发现 Chrome 能正确从缓存中读取 CSS 和 JS 文件,而 Firefox 却无法有效利用缓存,导致加载速度显著变慢。 ... [详细]
  • TCP协议中的可靠传输机制分析
    本文深入探讨了TCP协议如何通过滑动窗口和超时重传来确保数据传输的可靠性,同时介绍了流量控制和拥塞控制的基本原理及其在实际网络通信中的应用。 ... [详细]
  • Java实现:从键盘输入系数求解一元二次方程
    本教程详细介绍了如何使用Java编程语言,通过键盘输入系数a、b、c来求解一元二次方程ax² + bx + c = 0的根。 ... [详细]
  • 如何在PHP中安装Xdebug扩展
    本文介绍了如何从PECL下载并编译安装Xdebug扩展,以及如何配置PHP和PHPStorm以启用调试功能。 ... [详细]
  • 深入理解:AJAX学习指南
    本文详细探讨了AJAX的基本概念、工作原理及其在现代Web开发中的应用,旨在为初学者提供全面的学习资料。 ... [详细]
author-avatar
友爱锦锦_950
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有