java内存模型以及Volatile

本文深入解析Java内存模型,包括其目的、工作原理及其三大特性:可见性、原子性和有序性。详细介绍了工作内存与主存的概念,以及如何通过volatile关键字确保变量的可见性和禁止指令重排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java内存模型

Java内存模型是屏蔽掉各种硬件和操作系统的内存访问的差异,以实现Java程序在各种平台下都能达到一致的内存访问效果。
Java内存模型规定,所有的变量都是在主存中存储,所有的变量操作(读/写)都必须是在工作内存中进行。工作内存保存了线程中所有的变量,这些变量都是从主存中copy出来的。工作内存是每一个线程独有的内存,不能被其他线程所访问。线程间的值传递都是通过主存来完成的。
线程从主存中获取变量时是先保存在CPU缓存,然后在保存在工作内存中。
线程(工作内存)—CPU缓存–主存

可见性、原子性、有序性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

//线程1执行的代码
int i = 0;
i = 10;
//线程2执行的代码
j = i;

假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。
  此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.
  这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。
原子性一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

x = 10;         //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4

咋一看,可能会说上面的4个语句中的操作都是原子性操作。其实只有语句1是原子性操作,其他三个语句都不是原子性操作。
语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。
所以上面4个语句只有语句1的操作具备原子性。
也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
从上面可以看出,Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
有序性:即程序执行的顺序按照代码的先后顺序执行。
指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

volatile关键字

volatile修饰的变量(共享变量)来说,当变量被修改以后,就会强制写到主内存中。而当线程要读取变量时,就会去主内存中读取,而不是在工作内存中读取,这就有效的保证了线程之间变量的可见性。
volatile特性一:内存可见性,即线程A对volatile变量的修改,其他线程获取的volatile变量都是最新的。
volatile特性二:可以禁止指令重排序

参考文章

浅谈volatile关键字
Java内存模型
你真的了解volatile关键字吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值