【企业级理解】高效并发之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观察到,影响包括修改了内存中共享变量的值、发送了消息调用了方法等。

创作不易,点个赞吧~👍

最后的最后送大家一句话

白驹过隙,沧海桑田

与君共勉


相关文章
|
16天前
|
SQL 监控 数据可视化
完全开源!国内首个完全开源JAVA企业级低代码平台
JeeLowCode 是一款专为企业打造的 Java 企业级低代码开发平台,通过五大核心引擎(SQL、功能、模板、图表、切面)和四大服务体系(开发、设计、图表、模版),简化开发流程,降低技术门槛,提高研发效率。平台支持多端适配、国际化、事件绑定与动态交互等功能,广泛适用于 OA、ERP、IoT 等多种管理信息系统,帮助企业加速数字化转型。
|
17天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
12天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
35 6
|
15天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
16天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
35 2
|
17天前
|
存储 安全 Java
什么是 Java 的内存模型?
Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)规范的一部分,它定义了一套规则,用于指导Java程序中变量的访问和内存交互方式。
42 1
|
20天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
43 2
|
23天前
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
29 1
|
22天前
|
SQL 监控 数据可视化
完全开源!国内首个完全开源JAVA企业级低代码平台
JeeLowCode 是一款专为企业打造的 Java 企业级低代码开发平台,通过五大核心引擎(SQL、功能、模板、图表、切面)和四大服务体系(开发、设计、图表、模版),简化开发流程,降低技术门槛,提高研发效率。平台支持多端适配、国际化、事件绑定与动态交互等功能,广泛适用于 OA、ERP、IoT 等多种管理信息系统,帮助企业加速数字化转型。
完全开源!国内首个完全开源JAVA企业级低代码平台
|
26天前
|
监控 安全 Java
Java Z 垃圾收集器如何彻底改变内存管理
大家好,我是V哥。今天聊聊Java的ZGC(Z Garbage Collector)。ZGC是一个低延迟垃圾收集器,专为大内存应用场景设计。其核心优势包括:极低的暂停时间(通常低于10毫秒)、支持TB级内存、使用着色指针实现高效对象管理、并发压缩和去碎片化、不分代的内存管理。适用于实时数据分析、高性能服务器和在线交易系统等场景,能显著提升应用的性能和稳定性。如何启用?只需在JVM启动参数中加入`-XX:+UseZGC`即可。
145 0