黄石网站建设深圳网站建设论坛

惠州市家家庆家庭服务有限公司 2026/09/09 19:56:53

1. 引言:为什么需要内存模型?

想象一下这个场景:

public class VisibilityProblem {

private static boolean ready = false;

private static int number = 0;

public static void main(String[] args) {

new Thread(() -> {

while (!ready) {

// 空循环,等待ready变为true

}

System.out.println("Number: " + number);

}).start();

number = 42;

ready = true;

}

}

猜猜看:这个程序会输出什么?

你可能会说:"当然是42啊!" 但实际情况是:可能会无限循环,也可能输出0,甚至输出42!

为什么会这样?这就是Java内存模型要解决的核心问题。

2. 计算机体系结构的基础认知

2.1 现代计算机的"记忆系统"

我们的计算机并不是直接操作主内存的,而是有一个复杂的缓存体系:

CPU核心 → L1缓存 → L2缓存 → L3缓存 → 主内存

每个CPU核心都有自己的缓存,这就好比每个工作人员都有自己的笔记本,而不是所有人都直接在同一块黑板上写字。

2.2 Java内存模型的抽象

JMM是一个抽象概念,它定义了:

线程如何与主内存交互

什么时候写入会对其他线程可见

哪些操作顺序可以被重排序

// JMM的抽象视图

主内存 (共享)

↑↓

工作内存 (线程私有) ← 每个线程都有自己的工作内存

↑↓

CPU寄存器/缓存

3. 重排序:性能优化的双刃剑

3.1 什么是重排序?

重排序就是编译器和处理器为了优化性能,改变代码的实际执行顺序。

// 原始代码

int a = 1;

int b = 2;

int result = a + b;

// 可能的执行顺序(重排序后)

int b = 2; // 先执行

int a = 1; // 后执行

int result = a + b; // 结果仍然是3!

单线程下没问题,因为结果不变。但多线程下就可能出问题!

3.2 重排序的三种类型

编译器重排序 - 编译器觉得怎样快就怎样排

指令级并行重排序 - CPU同时执行多条指令

内存系统重排序 - 缓存机制导致的内存操作乱序

4. Happens-Before:Java的"因果律"

4.1 核心思想

Happens-Before解决了一个根本问题:如何确定一个线程的写操作对另一个线程可见?

4.2 六大规则详解

规则1:程序顺序规则

int x = 1; // 操作A

int y = x + 1; // 操作B - 一定能看到x=1

同一个线程内,前面的操作对后面的操作立即可见。

规则2:监视器锁规则

synchronized(lock) {

data = value; // 写操作

} // 解锁

// 其他地方

synchronized(lock) {

System.out.println(data); // 一定能看到上面的写入

} // 加锁

解锁操作happens-before后续的加锁操作。

规则3:volatile变量规则

volatile boolean flag = false;

int data;

// 线程A

data = 100;

flag = true; // volatile写

// 线程B

if (flag) { // volatile读

System.out.println(data); // 一定能看到100

}

volatile写happens-before后续的volatile读。

规则4:传递性规则

如果 A → B 且 B → C,那么 A → C。

规则5:start()规则

// 父线程

config = loadConfig(); // 操作A

Thread child = new Thread(() -> {

// 子线程中一定能看到config的初始化结果

useConfig(config); // 操作B

});

child.start(); // 操作C

A → C → B,因此 A → B。

规则6:join()规则

Thread child = new Thread(() -> {

result = compute(); // 操作A

});

child.start();

child.join(); // 操作B

useResult(result); // 操作C - 一定能看到A的结果

A → B → C,因此 A → C。

5. volatile关键字:轻量级同步利器

5.1 volatile的语义

public class VolatileExample {

private volatile boolean shutdown = false;

public void shutdown() {

shutdown = true; // 立即可见!

}

public void doWork() {

while (!shutdown) {

// 正常工作

}

}

}

volatile保证:

可见性:写操作立即对其他线程可见

有序性:禁止指令重排序

❌ 不保证原子性:count++ 仍然不是线程安全的

5.2 volatile的实现原理

JVM在volatile操作前后插入内存屏障:

写操作前:StoreStore屏障

写操作后:StoreLoad屏障

读操作前:LoadLoad屏障

读操作后:LoadStore屏障

6. 锁的内存语义:重量级但强大

6.1 锁的happens-before关系

public class LockExample {

private final Object lock = new Object();

private int sharedData;

public void writer() {

synchronized(lock) {

sharedData = 42; // 临界区内的操作

} // 释放锁

}

public void reader() {

synchronized(lock) { // 获取锁

System.out.println(sharedData); // 一定能看到42

}

}

}

锁释放 → 锁获取 建立了happens-before关系。

6.2 ReentrantLock的实现

public class ReentrantLockExample {

private final ReentrantLock lock = new ReentrantLock();

private int count;

public void increment() {

lock.lock();

try {

count++; // 受保护的操作

} finally {

lock.unlock(); // 释放锁,保证可见性

}

}

}

7. final域:不可变性的守护者

7.1 final的内存语义

public class FinalExample {

private final int immutableValue;

private int normalValue;

public FinalExample() {

normalValue = 1; // 可能被重排序到构造函数外

immutableValue = 42; // 禁止重排序到构造函数外!

}

}

final保证:对象引用可见时,final域一定已经正确初始化。

7.2 引用类型final的特殊性

public class FinalReferenceExample {

private final Map<String, String> config;

public FinalReferenceExample() {

config = new HashMap<>(); // 1. 写final引用

config.put("key", "value"); // 2. 写引用对象成员

// 1和2都不能重排序到构造函数外!

}

}

8. 双重检查锁定:从陷阱到救赎

8.1 错误版本:看似聪明实则危险

public class DoubleCheckedLocking {

private static Instance instance;

public static Instance getInstance() {

if (instance == null) { // 第一次检查

synchronized (DoubleCheckedLocking.class) {

if (instance == null) { // 第二次检查

instance = new Instance(); // 🚨 问题根源!

}

}

}

return instance;

}

}

问题根源:new Instance() 可能被重排序:

分配内存空间

赋值给instance引用 ← 此时instance不为null但对象未初始化!

初始化对象

8.2 正确方案1:volatile修复

public class SafeDoubleCheckedLocking {

private volatile static Instance instance; // ✅ 关键修复

public static Instance getInstance() {

if (instance == null) {

synchronized (SafeDoubleCheckedLocking.class) {

if (instance == null) {

instance = new Instance(); // ✅ 现在安全了

}

}

}

return instance;

}

}

8.3 正确方案2:静态内部类(推荐)

public class InstanceFactory {

private static class InstanceHolder {

static final Instance INSTANCE = new Instance(); // 由JVM保证线程安全

}

public static Instance getInstance() {

return InstanceHolder.INSTANCE; // 触发类初始化

}

}

JVM类初始化机制:天然线程安全!

9. 处理器差异与JMM的统一

9.1 不同处理器的内存模型

处理器 内存模型强度 允许的重排序

x86 强 (TSO) 只允许写-读重排序

ARM/PowerPC 弱 (RMO) 允许各种重排序

9.2 JMM的桥梁作用

JMM在弱内存模型处理器上插入更多内存屏障,在强内存模型处理器上插入较少屏障,为程序员提供一致的内存模型视图。

// 同一段Java代码在不同处理器上:

// x86: 可能只需要1个内存屏障

// ARM: 可能需要4个内存屏障

// 但JMM保证最终行为一致!

10. 实战指南:如何正确编写并发代码

10.1 并发编程的三层境界

第一层:无知者无畏

// 🚨 危险!数据竞争!

public class UnsafeCounter {

private int count = 0;

public void increment() { count++; }

public int getCount() { return count; }

}

第二层:过度同步

// ✅ 安全但性能差

public class SafeButSlowCounter {

private int count = 0;

public synchronized void increment() { count++; }

public synchronized int getCount() { return count; }

}

第三层:精准同步

// ✅ 安全且高效

public class OptimizedCounter {

private final AtomicInteger count = new AtomicInteger(0);

public void increment() { count.incrementAndGet(); }

public int getCount() { return count.get(); }

}

10.2 选择正确的工具

场景 推荐方案 原因

状态标志 volatile boolean 简单可见性需求

计数器 AtomicInteger 原子性操作

复杂同步 ReentrantLock 灵活性高

延迟初始化 静态内部类 简洁安全

集合操作 ConcurrentHashMap 专业工具

10.3 常见陷阱与解决方案

陷阱1:认为volatile保证原子性

private volatile int count = 0;

count++; // 🚨 不是原子操作!

解决方案:

private final AtomicInteger count = new AtomicInteger(0);

count.incrementAndGet(); // ✅ 原子操作

陷阱2:在构造函数中逸出this引用

public class ThisEscape {

public ThisEscape() {

BackgroundTask.start(this); // 🚨 危险!对象未完全构造

}

}

解决方案:

public class SafeConstruction {

private final Listener listener;

public SafeConstruction() {

listener = new Listener(); // 先完成构造

}

public void start() {

BackgroundTask.start(listener); // 然后安全发布

}

}

11. 总结:掌握JMM,成为并发高手

通过本文的学习,我们应该理解:

内存可见性不是自动的,需要正确同步

Happens-Before是理解Java并发的钥匙

volatile提供轻量级可见性保证

锁提供重量级但功能完整的同步

final正确使用可以提供初始化安全性

避免双重检查锁定陷阱,使用静态内部类方案

记住这个思维模型:

把多线程环境想象成一个团队协作项目:

每个线程就像团队成员

共享变量就像共享文档

同步机制就像会议和邮件通知

没有适当的沟通(同步),就会出现信息不一致!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

湖南营销型网站建设网站建设与网页制作

Odometer是一款专业的JavaScript数字动画库,能够以平滑流畅的方式实现数字之间的过渡效果。无论你是需要展示网站访问量、销售额数据,还是创建动态仪表盘ÿ

2026/06/30 14:01:38

中国建设银行官方网站东阳网站建设

AI图像生成成本分析:自建VS商用API费用对比在AI图像生成技术快速发展的今天,企业与开发者面临一个关键决策:是选择自建本地化生成系统,还是依

2026/06/30 13:00:04

黄石网站建设石家庄网站建设

Express框架基础与中间件应用1. Express基础项目回顾在构建小型项目时,我们使用了Express框架,它构建于Node的HTTP功能之上,抽象掉了很多粗糙的边缘部分。以下是一个小型留言簿项

2026/06/30 12:24:01

装饰网站建设容桂网站建设

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visu

2026/06/30 11:00:53

南通网站建设淮南网站建设

还在为B站直播姬的功能限制而烦恼吗?想要使用OBS等专业软件进行高质量直播,却不知道如何获取推流码?这个开源工具正是你需要的解决方案,让你轻松实

2026/06/30 11:42:57

住房城乡建设部网站网站建设要求

AtomicBoolean是 Java 并发包 (java.util.concurrent.atomic) 里的一个“线程安全布尔”。一句话:它就是一个可以安全地被多线程同时读/写的布尔

2026/06/30 10:16:19