Java并发编程 - volatile 怎么保障内存可见性 & 防止指令重排序?

简介: Java并发编程 - volatile 怎么保障内存可见性 & 防止指令重排序?

内存可见性

首先,要明确一下这个内存的含义,内存包括共享主存和高速缓存(工作内存),Volatile关键字标识的变量,是指CPU从缓存读取数据时,要判断数据是否有效,如果缓存没有数据,则再从主存读取,主存就不存在是否有效的说法了。而内存一致性协议也是针对缓存的协议。

内存可见性意思是一个CPU核心对数据的修改,对其他CPU核心立即可见,这句话拆开了理解:

1、CPU修改数据,首先是对工作内存的修改,也有人说被volatile修饰的变量不会拷贝副本到工作内存,而是直接修改主存,我觉得这个说法是不对的,CPU对数据的修改总是先修改工作内存,然后再同步回主内存,只不过是对被volatile修饰变量的修改,会立刻同步回主内存,假如只有一个线程修改volatile变量,那么这个变量在工作内存的副本会一直有效,CPU也不会每次修改都从主存读取volatile变量,只是每次修改后都会及时更新主存罢了。

2、对其他核心立即可见,这个的意思是,当一个CPU核心A修改完volatile变量,并且立即同步回主存,如果CPU核心B的工作内存中也缓存了这个变量,那么B的这个变量将立即失效,当B想要修改这个变量的时候,B必须从主存重新获取变量的值。

说了这么多,volatile有什么用呢?哎,这个作用一定要说清楚,不然很容易忘记!

举个例子:


public class VolatileTest implements Runnable {
    static boolean flag = true;
    @Override
    public void run() {
        while (flag) {
        }
        System.out.println("end......");
    }
    public static void main(String[] args) {
        new Thread(new VolatileTest()).start();
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = false;
        System.out.println("end main......");
    }
}
// 输出
end main......  

上面这个例子,子线程会一直卡住,原因就是flag不具备可见性,主线程和子线程刚开始都缓存了flag,且值是true,后来主线程把flag改成了false,但是子线程并不知道,仅此而已,仅此而已!!!!如果把flag用volatile修改,那么主线修改成false后,子线程再次while循环的时候,就会发现它缓存的flag已经失效了,它会去主存重新读取flag的值。

实现的原理一般都是基于CPU的MESI协议(缓存一致性协议),其中E表示独占Exclusive,S表示Shared,M表示Modify,I表示Invalid,如果一个核心修改了数据,那么这个核心的数据状态就会更新成M,同时其他核心上的数据状态更新成I,这个是通过CPU多核之间的嗅探机制实现的。

但是,这样是否就能保证多线程操作一个共享变量的时候,保证线程安全呢?其实不然,否则我怎么说是仅此而已呢!

volatile限定的是从缓存读取时刻的校验,如果两个CPU同时从各自缓存读取一个变量n=1(此时,变量n在各个CPU缓存上都是有效的),并且同时修改了变量n=n+1,再写回缓存,这个时候n的值等于2,而不是等于3。因此,在多线程操作共享变量(例如:计数器)的时候,正确的方式是使用同步或者Atomic工具类。

 

指令有序性

这个涉及到内存屏障(Memory Barrier),内存屏障有两个能力:

a、就像一套栅栏分割前后的代码,阻止栅栏前后的没有数据依赖性的代码进行指令重排序,保证程序在一定程度上的有序性。

b、强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效,保证数据的可见性。

首先,指令并不是代码行,指令是原子的,通过javap命令可以看到一行代码编译出来的指令,当然,像int i=1;这样的代码行也是原子操作。

在单例模式中,Instance inst = new Instance();   这一句,就不是原子操作,它可以分成三步原子指令:

  1. 分配内存地址
  2. new一个Instance对象
  3. 将内存地址赋值给inst

CPU为了提高执行效率,这三步操作的顺序可以是123,也可以是132,如果是132顺序的话,当把内存地址赋给inst后,inst指向的内存地址上面还没有new出来单例对象,这时候,如果就拿到inst的话,它其实就是空的,会报空指针异常。这就是为什么双重检查单例模式中,单例对象要加上volatile关键字。

 

内存屏障有三种类型和一种伪类型

a、lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。

b、sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见。

c、mfence,即全能屏障,具备ifence和sfence的能力。

d、Lock前缀:Lock不是一种内存屏障,但是它能完成类似全能型内存屏障的功能。

 

并发三特性总结


image.png

目录
相关文章
|
10月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1139 3
|
11月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
9月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
276 4
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
396 0
|
9月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
10月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
1063 17
|
11月前
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
629 2
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
350 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
645 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化