热门标签 | 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并发编程实战 童云兰 译

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



推荐阅读
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 深入解析:阿里实战 SpringCloud 微服务架构与应用
    本文将详细介绍 SpringCloud 在微服务架构中的应用,涵盖入门、实战和案例分析。通过丰富的代码示例和实际项目经验,帮助读者全面掌握 SpringCloud 的核心技术和最佳实践。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • 本文介绍了如何利用npm脚本和concurrently工具,实现本地开发环境中多个监听服务的同时启动,包括HTTP服务、自动刷新、Sass和ES6支持。 ... [详细]
  • 本文介绍了如何通过配置 Android Studio 和 Gradle 来显著提高构建性能,涵盖内存分配优化、并行构建和性能分析等实用技巧。 ... [详细]
  • 本文介绍了多个关于JavaScript的书籍资源、实用工具和编程实例,涵盖从入门到进阶的各个阶段,帮助读者全面提升JavaScript编程能力。 ... [详细]
  • 本文探讨了如何在日常工作中通过优化效率和深入研究核心技术,将技术和知识转化为实际收益。文章结合个人经验,分享了提高工作效率、掌握高价值技能以及选择合适工作环境的方法,帮助读者更好地实现技术变现。 ... [详细]
  • 开发笔记:9.八大排序
    开发笔记:9.八大排序 ... [详细]
  • PHP 过滤器详解
    本文深入探讨了 PHP 中的过滤器机制,包括常见的 $_SERVER 变量、filter_has_var() 函数、filter_id() 函数、filter_input() 函数及其数组形式、filter_list() 函数以及 filter_var() 和其数组形式。同时,详细介绍了各种过滤器的用途和用法。 ... [详细]
  • 本文详细探讨了JDBC(Java数据库连接)的内部机制,重点分析其作为服务提供者接口(SPI)框架的应用。通过类图和代码示例,展示了JDBC如何注册驱动程序、建立数据库连接以及执行SQL查询的过程。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 本文深入探讨了 Python 中的循环结构(包括 for 循环和 while 循环)、函数定义与调用,以及面向对象编程的基础概念。通过详细解释和代码示例,帮助读者更好地理解和应用这些核心编程元素。 ... [详细]
  • 本文总结了Java程序设计第一周的学习内容,涵盖语言基础、编译解释过程及基本数据类型等核心知识点。 ... [详细]
  • 探讨了小型企业在构建安全网络和软件时所面临的挑战和机遇。本文介绍了如何通过合理的方法和工具,确保小型企业能够有效提升其软件的安全性,从而保护客户数据并增强市场竞争力。 ... [详细]
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
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社区 版权所有