【企业级理解】高效并发之Java内存模型

简介: 【企业级理解】高效并发之Java内存模型

在讲解java内存模型之前我们有必要先了解一下物理计算机中的并发问题

一、硬件的效率与一致性

我们知道计算机的处理器肯定要与内存进行交互,如读取运算数据,存储运算结果等,这个IO操作是很难消除的(无法仅靠寄存器来完成所有运算任务)。由于计算机的存储设备与处理器的运算速度有着几个数量级的差距,所以现代计算机都不得不加入一层或多层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之前的缓存:将运算要使用的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了

由于引进了高速缓存,因此引入了一个新问题:缓存一致性问题,在多路处理器系统中,每个处理器都有自己的高速缓存,而他们又同时共享同一主内存,这种系统称为共享内存多核系统。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致;那我们如何解决呢:需要各个处理器访问缓存时都遵循一些协议,在读写时需要根据协议来操作,这类协议有MSI、MESI、MOSI、Synapse、Firefly及Dragon Protocol等。

二、Java内存模型

《Java虚拟机规范》中曾试图定义一种“java内存模型”来屏蔽各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的内存访问效果。

2.1 主内存与工作内存

java内存模型的主要目的是定义程序中的各种变量的访问规则,即关注在虚拟机中把变量存储到内存和从内存中取出变量值这样的底层细节。此处的变量与java编程中的变量还是有点区别的,它包括了实例字段、静态变量、静态字段和构成数组对象的原素,但是不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,也就不会存在竞争问题。

主内存:java内存模型规定了所有的变量都存储在主内存中

工作内存:每条线程都有自己的工作内存,线程的工作内存中被该线程使用的变量的主内存副本,线程对变量的所有操作(读取赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据。不通线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

2.2 内存间的交互操作

关于主内存和工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回主内存这一类的实现细节,java内存模型定义了一下8种操作来完成,具体如下:

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态
  • unlock(解锁):作用于主内存的变量,他把一个锁定的变量释放,释放的变量才能被其他线程锁定
  • read(读取):作用于主内存的变量,它把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作
  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用
  • write(写入):作用于主内存的变量,他把store操作从工作内存中得到的变量的值放入主内存的变量中

如果要把一个变量从主内存拷贝到工作内存,那就要按顺序执行read和load操作,如果要把变量从工作内存同步回主内存,就要按顺序执行store和write操作。

2.3 volatile型变量的特殊规则

关键字volatile是java虚拟机提供的最轻量级的同步机制,但它在代码中使用的并不多,接下来我们就来了解一下

当一个变量被修饰为volatile后,它具有两项特征:第一项是保证此变量对所有线程的可见性这里的可见性是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量并不能做到这一点,普通变量的值在线程间传递均需要通过主内存来完成。比如,线程A修改了一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A写完了之后再对主内存进行读取,新变量的值才会对线程B可见。

volatile的一致性问题:从物理存储的角度看,各个线程的工作内存中volatile变量也可以存在不一致的情况,但由于每次使用前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性的问题。但是java中的运算操作符并非原子操作,这导致volatile变量的运算在并发下一样是不安全的。

这里简单举例下并发下使用volatile的线程不安全行为:

通过volatile修饰一个静态变量i,对于一个非原子操作,比如 i++ ,反编译这行代码会得到只有一行代码的increase()方法在Class文件中是由四条字节码指令构成的,当getstatic指令把 i 的值取到操作栈顶时,volatile关键字保证了 i 的值在此时是正确的,但是在执行iconst_1,iadd这些指令的时候,其他线程可能已经把i的值改变了,而操作栈顶的值就变成了过期的数据。

那么什么时候适合用volatile关键字呢?

  • 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程来修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束

使用volatile变量的第二个语义是禁止指令重排优化,他的大概实现为:在volatile修饰的变量,修饰完之后相当于给变量增加了一个内存屏障,指令重排时不能把后面的指令重排序到内存屏障之前的位置。

关于指令重排的解释从硬件架构上讲,指令重排时指处理器采用了允许将多条指令不按程序规定的顺序分开发送给各个相应的电路单元进行处理。

其他并发情况,我们仍然需要通过加锁来保证原子性

volatile的性能

volatile变量读操作的性能消耗与普通变量几乎没有多大区别,但是写操作可能会慢上一些,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行(指令重排)

2.4 原子性、可见性与有序性

原子性

由java内存模型来直接保证原子性变量操作包括:read、load、assign、use、store和write这六个,我们大致可以认为基本类型的访问和读写都具备原子性。如果应用场景需要一个更大的范围来保证原子性,java内存模型还提供了lock和unlock操作来满足这种需求,虚拟机提供了lock和unlock的字节码指令monitorenter和monitorexit来隐示的使用这两个操作,这两个字节码反应到java代码中就是同步代码块–synchroized关键字,因此在synchroized代码块中也具备原子性。

可见性

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

java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量和volatile都是。

普通变量和volatile变量的区别是:volatile的特殊规则保证了新值能立即同步回主内存,以及每次使用前立即从主内存刷新,因此volatile保证了多线程操作时变量的可见性

有序性

java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排的语义,而synchronized则是由一个变量在同一时刻只允许一条线程对其进行lock操作这条规则获得的,这个规则决定了持有一个锁的两个同步块只能串行进入。

2.4 先行发生原则

java语言中有一个先行发生的原则,这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的非常有用的手段。

什么是先行发生原则?

先行发生原则是java内存模型中定义的两项操作之间的偏序关系,比如说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响被B观察到,影响包括修改了内存中共享变量的值、发送了消息调用了方法等。

创作不易,点个赞吧~👍

最后的最后送大家一句话

白驹过隙,沧海桑田

与君共勉


相关文章
|
27天前
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
33 6
|
18天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
21 0
|
28天前
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
20天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
33 8
|
18天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
22天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
48 5
|
20天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
20天前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
25天前
|
算法 Java 开发者
Java内存管理与垃圾回收机制深度剖析####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,特别是其垃圾回收机制的工作原理、算法及实践优化策略。不同于传统的摘要概述,本文将以一个虚拟的“城市环卫系统”为比喻,生动形象地揭示Java内存管理的奥秘,旨在帮助开发者更好地理解并调优Java应用的性能。 ####
|
26天前
|
Java
java内存区域
1)栈内存:保存所有的对象名称 2)堆内存:保存每个对象的具体属性 3)全局数据区:保存static类型的属性 4)全局代码区:保存所有的方法定义
22 1

热门文章

最新文章