当前位置: 首页 > article >正文

Java笔记——JMM

在多线程编程中共享变量的可见性、操作的原子性以及指令的重排序常常成为导致程序出现诡异Bug的罪魁祸首。而Java之所以能够成为并发编程的首选语言之一很大程度上归功于其强大的Java内存模型Java Memory Model, JMM。JMM不仅屏蔽了底层硬件和操作系统的差异还为开发者提供了一套清晰的内存访问规范让“编写一次到处运行”的并发代码成为可能。本文将深入剖析JMM的设计初衷、核心工作机制详解可见性、原子性、有序性三大并发问题并介绍volatile、synchronized以及CAS等解决方案的底层原理帮助你在并发编程中游刃有余。一、为什么需要Java内存模型现代计算机的CPU与内存之间存在着巨大的速度差异为了弥补这一鸿沟CPU引入了多级缓存L1、L2、L3。每个CPU核心都有自己私有的缓存当多个核心同时访问同一块内存数据时就会出现缓存一致性问题。此外为了提高执行效率编译器或处理器可能会对指令进行重排序。这些硬件层面的优化虽然极大提升了单线程性能却给多线程程序带来了不可预期的结果。不同的硬件架构如x86、ARM对缓存一致性和重排序的支持各不相同而Java作为跨平台语言必须屏蔽这些底层差异为上层提供统一的内存访问模型。Java内存模型正是这样一套抽象规范它定义了线程与主内存之间的交互规则保证了Java程序在不同平台上的行为一致性。二、JMM的核心设计主内存与工作内存JMM将内存划分为两个逻辑区域主内存Main Memory所有线程共享的内存区域存储Java对象的实例、静态变量等数据。工作内存Working Memory每个线程私有的内存区域线程对变量的所有操作读取、赋值都必须在工作内存中进行不能直接读写主内存中的变量。线程之间无法直接访问对方的工作内存变量值的传递必须通过主内存完成。当一个线程修改了自己工作内存中的变量副本后需要将其刷新到主内存其他线程才能从主内存中读取到最新值。这种“工作内存-主内存”的交互机制是理解并发问题的关键。JMM定义了8种原子操作来规范主内存与工作内存的交互lock作用于主内存将变量标识为线程独占状态。unlock作用于主内存释放被lock的变量。read作用于主内存将变量值从主内存传输到工作内存。load作用于工作内存将read操作获取的值放入工作内存的变量副本。use作用于工作内存将工作内存中的变量值传递给执行引擎。assign作用于工作内存将执行引擎返回的值赋给工作内存中的变量。store作用于工作内存将工作内存中的变量值传输到主内存。write作用于主内存将store操作的值写入主内存的变量。这些操作保证了变量在传递过程中的原子性但JMM并未限制虚拟机的实现是否允许某些操作合并因此实际性能优化仍然存在。三、JMM的三大并发问题基于上述内存模型多线程环境下会产生三类典型的并发问题1. 可见性问题定义一个线程对共享变量的修改其他线程无法立即看到。原因每个线程都有自己的工作内存对应CPU缓存线程修改变量时先修改工作内存再择机刷新到主内存。其他线程读取时可能仍从自己的缓存中获取旧值。示例public class VisibilityDemo { private static boolean flag true; public static void main(String[] args) throws InterruptedException { new Thread(() - { while (flag) { // 死循环 } System.out.println(线程退出); }).start(); Thread.sleep(1000); flag false; // 主线程修改flag但子线程可能永远看不到 } }上述代码中子线程可能永远无法退出因为主线程修改的flag值没有被子线程感知到。2. 原子性问题定义一个或多个操作在CPU执行过程中不可被中断的特性。在Java中对基本数据类型的读写操作是原子的long/double除外但自增操作如count并非原子操作。原因count在字节码层面包含读取-修改-写入三步线程可能在任意一步被切换导致最终结果出错。示例public class AtomicityDemo { private static int count 0; public static void main(String[] args) throws InterruptedException { Thread t1 new Thread(() - { for (int i 0; i 10000; i) count; }); Thread t2 new Thread(() - { for (int i 0; i 10000; i) count; }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); // 结果通常小于20000 } }3. 有序性问题定义程序执行的顺序与代码编写的顺序不一致。原因编译器和处理器为了优化性能可能会对指令进行重排序前提是重排序不会改变单线程的执行结果即遵守as-if-serial语义。但在多线程环境下重排序可能导致非预期的结果。示例public class OrderingDemo { private static int a 0, b 0, x 0, y 0; public static void main(String[] args) throws InterruptedException { for (int i 0; i 100000; i) { a 0; b 0; x 0; y 0; Thread t1 new Thread(() - { a 1; x b; }); Thread t2 new Thread(() - { b 1; y a; }); t1.start(); t2.start(); t1.join(); t2.join(); if (x 0 y 0) { System.out.println(发生重排序 i); break; } } } }在单线程视角下a1和xb的顺序无所谓但多线程环境下如果x0 y0说明两个线程中的赋值语句被重排序导致相互读到了初始值。四、JMM的解决方案happens-before原则与同步机制为了解决上述并发问题JMM提供了一套基于happens-before原则的规范并结合volatile、synchronized等同步机制来保证程序的正确性。1. happens-before原则定义若两个操作之间存在happens-before关系则第一个操作的结果对第二个操作可见且第一个操作的执行顺序在第二个操作之前。happens-before是JMM定义的一组偏序关系主要包括程序次序规则在一个线程内按照代码顺序前面的操作happens-before后面的操作。volatile变量规则对一个volatile变量的写操作happens-before后续对该变量的读操作。锁规则对一个锁的解锁操作happens-before后续对同一个锁的加锁操作。传递性若A happens-before B且B happens-before C则A happens-before C。线程启动规则Thread.start() happens-before该线程中的任何操作。线程终止规则线程中的任何操作happens-before其他线程检测到该线程终止如Thread.join()返回。中断规则对线程interrupt()的调用happens-before被中断线程检测到中断事件。对象终结规则对象的构造函数执行结束happens-before其finalize()方法。这些规则让开发者无需关心底层内存屏障的具体实现只需遵循规则就能写出正确的并发程序。2. volatile解决可见性与有序性作用保证可见性对volatile变量的写操作会立即刷新到主内存读操作会直接从主内存中读取。禁止指令重排序编译器会在volatile读写前后插入内存屏障防止其与前后代码重排序。底层实现在x86架构下volatile写操作会在汇编指令前加lock前缀该前缀会锁定缓存行并刷新到主内存同时阻止指令重排序。注意volatile不能保证原子性例如volatile int count; count仍然不是原子操作。适用场景状态标志如boolean开关双重检查锁Double-Checked Locking中的单例对象独立观察如读取配置参数3. synchronized保证原子性、可见性与有序性作用原子性synchronized修饰的代码块或方法在同一时刻只能有一个线程执行保证了代码块的原子性。可见性线程在进入synchronized块时会清空工作内存从主内存重新读取变量退出时会将工作内存中的修改刷新到主内存。有序性synchronized块内部依然可能重排序但由于只有一个线程执行不会产生有序性问题从外部看synchronized块内的代码整体上不会与块外重排序。底层实现synchronized依赖JVM的monitor机制编译后会在同步块前后插入monitorenter和monitorexit指令通过锁的互斥保证原子性。4. CASCompare-And-Swap无锁原子操作定义CAS是一种乐观锁技术它包含三个操作数内存位置V、预期原值A、新值B。只有当V的值等于A时才将V更新为B否则什么都不做。整个操作是原子的。Java实现java.util.concurrent.atomic包中的原子类如AtomicInteger底层使用CAS实现通过Unsafe类的compareAndSwapInt等本地方法调用CPU的CAS指令如x86的cmpxchg。ABA问题如果V的值从A变为B再变回ACAS会误认为没有变化。可通过添加版本号如AtomicStampedReference解决。适用场景轻量级并发场景如计数器、状态标志等避免锁带来的上下文切换开销。五、总结Java内存模型是理解并发编程的基石。它通过定义主内存与工作内存的交互规则以及happens-before原则为开发者屏蔽了底层硬件的复杂性。在实际开发中当需要保证可见性和有序性时可以使用volatile关键字。当需要保证原子性时可以使用synchronized或java.util.concurrent.locks.Lock。对于高并发下的计数器等场景可以使用Atomic系列类基于CAS。掌握JMM不仅能帮你写出线程安全的代码还能让你在面对各种诡异的并发Bug时拥有快速定位问题的能力。希望本文能为你打开并发编程世界的一扇窗让你在Java并发之路上走得更稳、更远。

相关文章:

Java笔记——JMM

在多线程编程中,共享变量的可见性、操作的原子性以及指令的重排序,常常成为导致程序出现诡异Bug的罪魁祸首。而Java之所以能够成为并发编程的首选语言之一,很大程度上归功于其强大的Java内存模型(Java Memory Model, JMM&#xff…...

Open Images数据集完全指南:从零开始构建计算机视觉应用

Open Images数据集完全指南:从零开始构建计算机视觉应用 【免费下载链接】dataset The Open Images dataset 项目地址: https://gitcode.com/gh_mirrors/dat/dataset Open Images数据集是谷歌推出的一个大规模计算机视觉数据集,包含约900万张图像…...

5分钟搞定黑苹果音频驱动:AppleALC新手配置指南

5分钟搞定黑苹果音频驱动:AppleALC新手配置指南 【免费下载链接】AppleALC Native macOS HD audio for not officially supported codecs 项目地址: https://gitcode.com/gh_mirrors/ap/AppleALC AppleALC是一款强大的开源内核扩展工具,能让非官方…...

Kimi-VL-A3B-Thinking Chainlit定制化开发:添加历史记录/多用户会话/图片标注功能

Kimi-VL-A3B-Thinking Chainlit定制化开发:添加历史记录/多用户会话/图片标注功能 1. 项目背景与模型介绍 Kimi-VL-A3B-Thinking是一款基于混合专家架构(MoE)的开源视觉语言模型(VLM),在多模态推理和长上下文理解方面表现出色。该模型仅激活2.8B参数就…...

双模型对比:OpenClaw同时接入nanobot与云端API的性能测试

双模型对比:OpenClaw同时接入nanobot与云端API的性能测试 1. 测试背景与目标 最近在尝试用OpenClaw搭建一个能同时处理本地轻量任务和复杂云端任务的智能助手系统。核心需求是:日常简单查询走本地部署的轻量模型(nanobot)&#…...

Unsloth Docker部署详解:从零开始搭建训练环境

Unsloth Docker部署详解:从零开始搭建训练环境 1. 环境准备与Docker安装 1.1 系统要求检查 在开始之前,请确保你的系统满足以下基本要求: 64位Linux系统(推荐Ubuntu 22.04)NVIDIA显卡驱动已安装(建议版…...

CANoe实战:手把手教你用J1939.dbc发送超8字节长帧报文(附完整CAPL代码)

CANoe实战:J1939长帧报文分包发送全解析与CAPL代码优化 在汽车电子开发领域,J1939协议作为商用车通信标准,其长帧报文处理一直是工程师面临的典型挑战。当数据长度超过CAN总线单帧8字节限制时,如何高效实现分包传输?本…...

SVG-Edit:开源矢量编辑在浏览器工具中的创新实践

SVG-Edit:开源矢量编辑在浏览器工具中的创新实践 【免费下载链接】svgedit Powerful SVG-Editor for your browser 项目地址: https://gitcode.com/gh_mirrors/sv/svgedit SVG-Edit是一款基于浏览器环境的开源矢量图形编辑工具,提供在线SVG编辑能…...

别再死记硬背公式了!用3Blue1Brown的几何动画,5分钟搞懂行列式到底是啥

用动画解锁行列式的几何直觉:从死记硬背到可视化理解 当你第一次在课本上看到行列式的计算公式时,是否感到困惑——这个看似随意的ad-bc到底意味着什么?为什么它能够决定矩阵是否可逆?传统教学往往让我们陷入计算的泥潭&#xff0…...

Linux服务器运维:5个最容易被忽略的故障排查技巧(附实战命令)

Linux服务器运维:5个最容易被忽略的故障排查技巧(附实战命令) 在Linux服务器运维的日常工作中,有些故障排查点往往被工程师们忽视,直到问题爆发才追悔莫及。本文将揭示五个最容易被忽略但至关重要的排查技巧&#xff…...

JDK17下Lombok报错?手把手教你解决IllegalAccessError问题(附最新版本配置)

JDK17与Lombok兼容性实战:彻底解决IllegalAccessError的终极指南 最近在将项目迁移到JDK17时,不少开发者反馈遇到了一个棘手的错误:java.lang.IllegalAccessError,特别是与Lombok相关的模块访问问题。这个错误看似简单&#xff0c…...

银河麒麟服务器系统4.02-sp2实战:飞腾架构下的虚拟机优化与远程管理

1. 银河麒麟服务器系统与飞腾架构概述 银河麒麟服务器系统4.02-sp2是国内自主研发的企业级操作系统,特别针对飞腾处理器架构进行了深度优化。飞腾作为国产CPU的代表之一,采用ARMv8指令集,在政务、金融等关键领域广泛应用。这套组合最大的特点…...

EMI滤波器选型指南:从共模与差模噪声到实际应用场景

1. EMI滤波器的核心作用与选型挑战 刚入行那会儿,我负责的第一个电源项目就栽在了EMI测试上。设备一上电,测试仪器的曲线就像心电图发作似的疯狂跳动。当时 mentor 只说了一句:"去查查共模和差模的区别"。这句话成了我后来十年硬件…...

像素幻梦工坊实战案例:为开源像素游戏引擎PixiJS提供AI素材管道

像素幻梦工坊实战案例:为开源像素游戏引擎PixiJS提供AI素材管道 1. 项目背景与价值 在游戏开发领域,像素艺术因其独特的复古魅力和相对较低的制作成本,始终保持着旺盛的生命力。然而传统像素素材创作需要艺术家逐像素绘制,耗时耗…...

gte-base-zh场景应用:电商搜索与客服问答的语义匹配实战

gte-base-zh场景应用:电商搜索与客服问答的语义匹配实战 1. 电商场景中的语义匹配挑战 1.1 搜索不精准的痛点分析 在电商平台上,用户搜索"苹果手机"却看到水果苹果的图片,或者输入"轻薄笔记本"却返回游戏本&#xff0…...

PHPMailer OAuth2认证终极指南:安全挑战与架构实践深度解析

PHPMailer OAuth2认证终极指南:安全挑战与架构实践深度解析 【免费下载链接】PHPMailer The classic email sending library for PHP 项目地址: https://gitcode.com/GitHub_Trending/ph/PHPMailer PHPMailer作为PHP领域最经典的邮件发送库,其OAu…...

RWKV7-1.5B-g1a开源大模型落地:无需高端A100,RTX4090即可跑满多语言生成能力

RWKV7-1.5B-g1a开源大模型落地:无需高端A100,RTX4090即可跑满多语言生成能力 1. 模型简介 rwkv7-1.5B-g1a 是基于新一代 RWKV-7 架构的开源多语言文本生成模型,专为实际应用场景优化。这个1.5B参数的模型在保持出色生成能力的同时&#xff0…...

终极指南:如何快速搭建NixOS配置开发环境 [特殊字符]

终极指南:如何快速搭建NixOS配置开发环境 🚀 【免费下载链接】linux-nixos-hyprland-config-dotfiles Linux 🐧 configuration based on NixOS ❄️, Hyprland, and Catppuccin Macchiato theme 😸 for a consistent, complete, a…...

STM32HAL库项目实战:我把W5500和MQTTClient库‘缝’起来,实现了阿里云OTA升级前传

STM32HAL库与W5500深度整合:从MQTT云连接到OTA升级的工程实践 在嵌入式设备智能化浪潮中,远程固件升级(OTA)已成为工业设备的标配功能。本文将揭示如何基于STM32HAL库和W5500以太网芯片构建可靠的云连接通道,为后续OTA升级打下坚实基础。不同…...

Qwen3-0.6B-FP8快速上手:无需CUDA环境的CPU友好型大模型对话工具指南

Qwen3-0.6B-FP8快速上手:无需CUDA环境的CPU友好型大模型对话工具指南 想体验大模型对话,但被动辄几十GB的模型和昂贵的显卡劝退?今天给大家介绍一个“小钢炮”——Qwen3-0.6B-FP8对话工具。它只有6亿参数,经过FP8量化后体积小巧&…...

SecGPT-14B部署教程:模型热更新机制设计,不中断服务切换安全知识版本

SecGPT-14B部署教程:模型热更新机制设计,不中断服务切换安全知识版本 1. SecGPT-14B简介 SecGPT是由云起无垠推出的开源大语言模型,专门针对网络安全领域设计。这个模型融合了自然语言理解、代码生成和安全知识推理等核心能力,能…...

清音刻墨镜像免配置亮点:内置10+中文领域词典(医疗/法律/IT)开箱即用

清音刻墨镜像免配置亮点:内置10中文领域词典(医疗/法律/IT)开箱即用 1. 为什么字幕对齐需要专业词典? 做视频字幕的朋友都知道,最头疼的不是生成文字,而是让文字和声音完美对齐。普通字幕工具遇到专业术语…...

PCL点云凹包计算实战:从2D投影到3D建模的Alpha-Shape算法解析

1. Alpha-Shape算法:点云凹包计算的灵魂 第一次接触点云凹包计算时,我被这个看似简单实则精妙的问题难住了。传统凸包算法就像给点云套上一个紧绷的橡皮筋,而实际项目中我们经常需要保留物体表面的凹陷特征。这时候Alpha-Shape算法就派上了大…...

FTDI FT2232H USB转JTAG实战指南:MPSSE配置与多设备调试

1. FT2232H与JTAG基础入门 第一次接触FT2232H这块芯片时,我完全被它的多功能性震惊了。这块小小的USB转接芯片不仅能处理UART通信,还能通过MPSSE引擎模拟JTAG、SPI、I2C等多种协议。对于嵌入式开发者来说,这简直就是调试神器。 FT2232H最吸引…...

RustFS集群部署避坑指南:我用Ansible踩过的3个坑及解决方案

RustFS集群部署实战:Ansible自动化中的三大典型问题与深度解决方案 当你在凌晨三点收到集群告警通知时,会不会希望当初的部署方案能更健壮些?作为经历过数十次生产环境部署的老兵,我想分享那些官方文档不会告诉你的实战经验。本文…...

vLLM-v0.17.1实战案例:HuggingFace模型无缝接入+多LoRA高效推理

vLLM-v0.17.1实战案例:HuggingFace模型无缝接入多LoRA高效推理 1. vLLM框架简介 vLLM是一个专为大型语言模型(LLM)设计的高性能推理和服务库,由加州大学伯克利分校的天空计算实验室(Sky Computing Lab)开发,现已发展为社区驱动的开源项目。…...

Llama-3.2V-11B-cot镜像免配置:内置模型加载进度条与超时重试机制

Llama-3.2V-11B-cot镜像免配置:内置模型加载进度条与超时重试机制 1. 项目概述 Llama-3.2V-11B-cot是基于Meta Llama-3.2V-11B-cot多模态大模型开发的高性能视觉推理工具,专为双卡4090环境深度优化。这个工具解决了传统大模型部署中的多个痛点&#xf…...

OpenClaw安全配置要点:Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF本地运行权限管理

OpenClaw安全配置要点:Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF本地运行权限管理 1. 为什么需要特别关注OpenClaw的安全配置? 第一次在本地部署OpenClaw时,我犯了一个新手常见的错误——直接使用默认配置启动服务。结果第二天…...

算法 POJ1029

一.题目大意假币描述“金条”银行从可靠来源收到的信息,在他们最后一组的 N 枚硬币中,恰好有一枚硬币是假的,并且重量与其他硬币不同(而所有其他硬币的重量都相同)。经济危机之后,他们只有一个简…...

StructBERT-Large中文相似度工具一文详解:三级匹配等级判定逻辑与业务适配建议

StructBERT-Large中文相似度工具一文详解:三级匹配等级判定逻辑与业务适配建议 本文深度解析StructBERT-Large中文相似度工具的核心匹配逻辑,提供实际业务场景中的适配建议和优化方案 1. 工具核心价值与适用场景 StructBERT-Large中文相似度工具是一个基…...