Java内存模型

什么是Java内存模型

       我们知道,Java程序是需要运行在Java虚拟机上面的,Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

      Java 内存模型中规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存(类比缓存理解),线程的工作内存中保存了该线程使用到主内存中的变量拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递(通信)均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示:

       这里面提到的主内存和工作内存,读者可以简单的类比成计算机内存模型中的主存和缓存的概念。特别需要注意的是,主内存和工作内存与JVM内存结构中的Java堆、栈、方法区等并不是同一个层次的内存划分,无法直接类比。《深入理解Java虚拟机》中认为,如果一定要勉强对应起来的话,从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分。工作内存则对应于虚拟机栈中的部分区域。

这个图与计算机内存的的模型很相似,简单对比如下:

 

Java内存模型需要解决的问题

缓存一致性问题:

由于单个线程在更新共享变量时都是先更新自己工作内存中的变量副本,然后再同步到主内存中,这就不可避免地造成了不同线程的工作内存中同一共享变量值不一致的情况。

指令重排(类似于cpu的处理优化):

除了现在很多流行的处理器会对代码进行优化乱序处理,很多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排,举几个例子:

例子1:简单指令重排
假设有这么两个共享变量a和b:

private int a;
private int b;

在线程A中有两条语句对这两个共享变量进行赋值操作:

a = 1;
b = 2;

假设当线程A对a进行复制操作的时候发现这个变量在主内存已经被其它的线程加了访问锁,
那么此时线程A怎么办?等待释放锁?不,等待太浪费时间了,它会去尝试进行b的赋值操作,
b这时候没被人占用,因此就会先为b赋值,再去为a赋值,那么执行的顺序就变成了:

b = 2;
a = 1;


例子2:A线程指令重排导致B线程出错
对于在同一个线程内,这样的改变是不会对逻辑产生影响的,但是在多线程的情况下指令重排序会带来问题。看下面这个情景:

在线程A中:
context = loadContext();
inited = true; 

在线程B中:
while(!inited ){ //根据线程A中对inited变量的修改决定是否使用context变量
sleep(100);

}

doSomethingwithconfig(context);

假设线程A中发生了指令重排序:
inited = true;
context = loadContext();

那么B中很可能就会拿到一个尚未初始化或尚未初始化完成的context,从而引发程序错误。

       所以,再来总结下,JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

Java内存模型的实现

      了解Java多线程的朋友都知道,在Java中提供了一系列和并发处理相关的关键字,比如volatilesynchronizedfinalconcurren包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字。实际上java解决线程并发问题的方式即是通过保证如下几个特性:

原子性

在Java中,为了保证原子性,提供了锁机制(如 synchronized,ReentryLock等)以及CAS,区别在于前者是以线程独占的方式处理,后者则是以类似乐观锁的方式处理。

可见性(解决缓存一致性问题)

Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

除了volatile,Java中的synchronizedfinal两个关键字也可以实现可见性。只不过实现方式不同,这里不再展开了。

有序性(解决指令重排问题)

在Java中,可以使用synchronizedvolatile来保证多线程之间操作的有序性。实现方式有所区别:volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

 

参考文档:

http://www.hollischuang.com/archives/2550

https://www.cnblogs.com/YJK923/p/10478716.html

https://www.2cto.com/kf/201601/487117.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值