讲师中心 微信公众号
AI工具推荐 视频效率加速

Java里什么是可见性问题_Java内存模型说明

胖宇同学_1169

胖宇同学_1169

发布时间:2025-12-30 13:03:09

|

342人浏览过

|

来源于php中文网

原创

volatile通过强制读写主内存和插入内存屏障解决可见性与有序性,但不保证复合操作原子性;需结合锁或原子类保障原子性。

java里什么是可见性问题_java内存模型说明

Java里的可见性问题,就是“一个线程改了值,另一个线程死活看不见”——不是没改,是改了但没“发布”出去。

为什么 flag = true 后读线程还在死循环?

因为每个线程都有自己的工作内存(比如 CPU 缓存),flag 被修改后可能只写进了写线程的本地缓存,根本没刷到主内存;而读线程一直从自己缓存里取旧值 false,永远等不到变化。

  • 线程结束、Thread.sleep()、Thread.yield() 都不构成 happens-before 关系,不能保证可见性
  • JVM 和 CPU 为提速会做指令重排序:比如你写的 number = 42; ready = true;,实际可能变成 ready = true; number = 42;,导致读线程看到 ready == true 却读到 number == 0
  • 编译器甚至会把 while (!flag) 优化成寄存器轮询(不重新读内存),尤其在循环体为空或只有 yield() 时

volatile 怎么解决可见性?它不是万能的

volatile 强制每次读都从主内存加载,每次写都立即刷回主内存,并插入内存屏障禁止相关重排序——但它只保单次读/写操作的可见性与有序性,不保复合操作原子性。

java-api-doc-generator
java-api-doc-generator

分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。

下载
  • 适合场景:boolean running 状态开关、轻量级通知标志、一写多读的简单变量
  • 不适合:count++(读-改-写三步,非原子)、依赖上下文的判断(如 if (flag) doSomething(); 中的 doSomething() 不受保护)
  • 不能和 final 同时修饰一个字段:前者允许运行时修改并保证可见,后者禁止修改,语义冲突

比 volatile 更强的方案:锁与原子类

当需要同时保障可见性 + 原子性(比如计数、状态切换+副作用执行),就得上更重的同步机制。

立即学习“Java免费学习笔记(深入)”;

  • synchronized 块进出时会清空/刷新工作内存中被锁保护的所有变量,天然建立 happens-before,且可嵌套、可重入
  • ReentrantLock 提供相同内存语义,还支持超时、中断、公平性等控制,但必须严格配对 lock()/unlock()
  • AtomicInteger 等原子类底层用 volatile + CAS,既可见又原子,适合高性能计数、状态位操作
  • ConcurrentHashMap 内部已做内存屏障处理,比 Collections.synchronizedMap() 更安全高效;后者只锁方法,不保复合操作(如 if (!map.containsKey(k)) map.put(k, v); 仍可能并发出错)
public class VisibilityFix {
    private static volatile boolean flag = false; // ✅ 解决可见性
    private static AtomicInteger counter = new AtomicInteger(0); // ✅ 可见+原子

    public static void main(String[] args) throws InterruptedException {
        Thread reader = new Thread(() -> {
            while (!flag) {
                // 不再空转,volatile 保证每次读主内存
            }
            System.out.println("counter = " + counter.get()); // 一定看到最新值
        });

        Thread writer = new Thread(() -> {
            try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
            flag = true;
            counter.incrementAndGet();
        });

        reader.start();
        writer.start();
        reader.join();
    }
}

最常被忽略的一点:可见性不是独立存在的——它总和有序性、原子性纠缠在一起。只加 volatile 却忘了重排序风险,或以为加了锁就万事大吉却漏掉复合操作的临界区覆盖,都会让程序在高并发下偶然失效,极难复现和调试。

热门AI工具

更多
超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

相关专题

更多
java中boolean的用法
java中boolean的用法

在Java中,boolean是一种基本数据类型,它只有两个可能的值:true和false。boolean类型经常用于条件测试,比如进行比较或者检查某个条件是否满足。想了解更多java中boolean的相关内容,可以阅读本专题下面的文章。

2150

2023.11.13

java boolean类型
java boolean类型

本专题整合了java中boolean类型相关教程,阅读专题下面的文章了解更多详细内容。

163

2025.11.30

counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3628

2023.11.20

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

354

2023.09.25

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

354

2023.09.25

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

814

2025.10.23

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4598

2023.08.10

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

583

2025.12.01

Docker Desktop怎么设置端口映射
Docker Desktop怎么设置端口映射

本专题聚焦Docker Desktop容器端口映射与访问配置,整理从创建容器、设置端口绑定到验证本地服务访问的操作流程。针对端口被占用、映射配置错误、容器服务无法访问等情况,也提供相应的检查方向,方便用户完成端口配置。

0

2026.11.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn