最简单的 Java内存模型 讲解



本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。

并发编程系列博客传送门


前言

在网上看了很多文章,也看了好几本书中关于JMM的介绍,我发现JMM确实是Java中比较难以理解的概念。网上很多文章中关于JMM的介绍要么是照搬了一些书上的内容,要么就干脆介绍的就是错的。本文试着用比较简洁的语言介绍清楚JMM到底是什么,解决了Java编程中的哪些问题。不求深入,但求让读者看地清楚,看完之后能对JMM有个比较直观的认识。

本文是笔者在总结了网上的多篇文章之后加上自己的理解整理出来的,内容上可能和JMM标准存在偏差,有问题还望留言指出。

什么是JMM

JMM是一个规范,我从JSR113标准中摘录了一段对JMM的简单介绍:

JavaTM virtual machines support multiple threads of execution. Threads are represented by the
Thread class. The only way for a user to create a thread is to create an object of this class; each
thread is associated with such an object. A thread will start when the start() method is invoked
on the corresponding Thread object.
The behavior of threads, particularly when not correctly synchronized, can be confusing and
counterintuitive. This specification describes the semantics of multithreaded programs written in
the JavaTM programming language; it includes rules for which values may be seen by a read of
shared memory that is updated by multiple threads. As the specification is similar to the memory
models for different hardware architectures, these semantics are referred to as the JavaTM memory
model.
These semantics do not describe how a multithreaded program should be executed. Rather,
they describe the behaviors that multithreaded programs are allowed to exhibit. Any execution
strategy that generates only allowed behaviors is an acceptable execution strategy.

上面的英文简要翻译如下:

Java虚拟机支持多线程执行。在Java中Thread类代表线程,创建一个线程的唯一方法就是创建一个Thread类的实例对象。当调用了对象的start方法后,相应的线程将会执行。

线程的行为有时会令人困惑而且和我们的直觉相左,特别是在线程没有正确同步的情况下。本规范描述了JVM平台上多线程程序的语义(含义),具体包括一个线程对共享变量的写入何时能被其他线程“看到”。由于本规范和不同硬件平台上的内存模型相似,所以将本规范命名为Java内存模型。

从上面这段英文介绍中我们可以得到关于JMM的简要信息:

  • JMM是一个和多线程相关的规范;
  • JMM描述了JVM平台上多线程程序的语义(含义),具体包括一个线程对共享变量的写入何时能被其他线程“看到”。

但是只看上面对于JMM的简单解释,我相信大多数人还是会很晕,对JMM具体是什么还是很模糊。

不过我在上面的这段介绍中又发现了一段对JMM介绍的关键信息:

As the specification is similar to the memory models for different hardware architectures, these semantics are referred to as the JavaTM memory model. (JMM和硬件平台上的内存模型相似)

上面的介绍中提到JMM和硬件平台上的内存模型相似,那么我们就先看看硬件平台上的内存模型究竟是什么?

内存模型

有点计算机基础的同学都应该知道,程序执行的时候其实就是一条条指令在CPU上执行的过程,而指令的执行又势必会涉及到数据的读取和写入。说到数据,就又不得不提到一个重要的硬件:内存。在计算机中,内存是数据的“收集站”,数据从键盘、网络、文件也有可能是一些传感器设备进入到内存,然后CPU从内存中读取这些数据并对这些数据进行“加工”后再写回到内存。

上面整个过程看起来很完美,但是就像人与人之间是有差别的一样,硬件和硬件之间也存在差别。CPU的运行速度就和尤塞恩·博尔特的速度一样(飞一样的速度),而内存的运行速度和CPU相比就像我的跑步速度和博尔特比一样,根本不是一个数量级的。CPU和内存运行速度的差距会导致整个系统性能的下降,因为CPU每次读写数据都要等待内存。(木桶理论在计算机中的体现)

但是这个问题根本就难不倒我们伟大的硬件工程师们。“聪明”的工程师们在CPU中加入了一层CPU高速缓存层。这个缓存的运算速度和CPU相当,当指令在CPU上运行的时候,会先将运算需要的数据从内存中复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。(现代CPU其实是有多级缓存的,但是为了简单起见就没介绍了,因为我觉得这里不介绍CPU多级缓存不会影响对JMM的理解)

世界好像又重归于平静,一切又显得那么美好。但是其实问题才刚刚开始。

原子性问题

上面提到CPU进行运算时需要将共享变量先加载到CPU缓存中,运算结束后再将最新数据写回共享内存。这种看起来完美的工作方式其实存在一个问题,下面我们就以上面的图片为列子,说下这个问题。

假如现在系统环境是 单核CPU+多线程工作模式,共享变量初始值是1,线程1和线程2分别对这个共享变量进行加一操作,理论上这个共享变量最后的值是3。我们看看程序的执行行为是否会和我们预期的一致。

线程对一个共享变量加一的过程需要分三步进行:

step1: read共享变量到工作内存
step2:对共享变量+1
step3:将共享变量写回主内存

但是上面的三个步骤并不是原子操作,也就是说可能会被打断。现在假如线程1已经执行完了step1,但是这时CPU时间片用完了,线程2获得执行机会也从内存中加载共享变量的值(此时共享变量的值还是1),最后两个线程执行完step2和step3之后共享变量的值是2,并不是3。

出现上面问题的原因就是对共享变量的加一操作并不是原子性操作,所谓原子性操作是指一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。在多线程环境下原子性问题可能会造成错误的执行结果。

原子性问题是内存模型存在的第一个问题,但是内存模型存在的问题不止这一个。

缓存一致性问题

随着科技的进步,对CPU的需求越来越高。但是摩尔定律的失效注定单个CPU的性能已经很难再大幅度提升。此时“聪明”的硬件工程师又出场了,他们创造性地将多个CPU集成到一个上,这样CPU的性能不就能成倍地增长了么。多核CPU的确带来了CPU性能的提升,但是这却“害苦”了软件工程师,因为多核CPU大大提升了多线程编程的难度。

多核CPU进行多线程编程时存在的一个显著问题就是缓存一致性问题

以上图为例,在多核CPU多线程环境下,两个线程对共享变量a进行加1操作。两个线程都将共享变量a在内存中的值加载到了工作内存中,如上图所示。但是此时线程2失去了CPU时间片,而线程1还是继续执行并成功将变量加一。当线程1执行完之后,内存中的值如下图所示:

我们发现此时线程2中的变量a的值已经是过期的值,并不是变量a最新的值,所以当线程2执行完之后变量a并不是我们想要的值3。这个问题就是多核CPU中缓存一致性问题。

和上面的原子性问题不同,缓存一致性问题只有在多核多线程环境下才会出现,而原子性问题只要是在多线程环境下都可能会出现。

指令重排序问题

所谓的指令重拍是指CPU为了是内部的处理器单元得到充分的应用,可能会对代码进行乱序执行的行为。这个指令重拍的行为在单线程环境下不会有任何问题,但是在多线程环境下程序就可能出现错误的执行结果。

这边不准备会指令重排进行深入的讨论,大家只要知道指令重排序是一种CPU性能优化的行为,而这个行为在多线程环境下可能会导致程序错误的执行结果。

通过上面分析我们看到:随着CPU性能的不断提升,随之出现了原子性问题、缓存一致性问题和指令重排序问题。细心的我们会发现这些问题其实是和多线程环境下共享变量访问的原子性、可见性和有序性问题一一对应的。

内存模型的作用

为了既保证CPU的高效执行,有保证共享内存读写的正确性(原子性、可见性和有序性),人们定义了内存模型。内存模型是一个规范,这个规范能保证共享内存读写的正确性。

Java内存模型

上面提到内存模型的出现是为了解决共享变量读写的原子性、可见性和有序性问题,但是没有具体讲怎么解决的。下面就来看看在Java中的内存模型JMM。

Java内存模型是内存模型在Java语言中的体现。这个模型的主要目标是定义程序中各个共享变量的访问规则,也就是在虚拟机中将变量存储到内存以及从内存中取出变量这类的底层细节。通过这些规则来规范对内存的读写操作,保证了并发场景下的可见性、原子性和有序性。

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

而JMM就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步。

以上图片来自(https://www.hollischuang.com/archives/2550)

简单总结

JMM是一个和多线程编程的相关概念,是内存模型在Java平台上的体现。这个模型保证了在多核CPU多线程编程环境下,对共享变量读写的原子性、可见性和有序性。

本篇博客只是简单讲了下JMM的概念,以及解决哪些问题。具体JMM怎么解决原子性、可见性和有序性问题的,后续会写博客分析。

参考

原文地址:https://www.cnblogs.com/54chensongxia/p/12022648.html

时间: 2024-10-08 22:44:43

最简单的 Java内存模型 讲解的相关文章

Java内存模型JMM简单分析

参考博文:http://blog.csdn.net/suifeng3051/article/details/52611310 http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html    http://www.cnblogs.com/dolphin0520/p/3613043.html 一.Java内存区域的划分 由于Java程序是交给JVM执行的,所以我们在谈Java内存区域分析的时候事实上是指JVM内存区域划分. 根

java内存模型:简单理解

1.Java内存模型(Java Memory Model,JMM) 2.JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory). 本地内存中存储了该线程以读/写共享变量的副本,本地内存是JMM的一个抽象概念,并不真实存在.它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化. 3.指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺

Java内存模型与垃圾回收

1.Java内存模型 Java虚拟机在执行程序时把它管理的内存分为若干数据区域,这些数据区域分布情况如下图所示: 程序计数器:一块较小内存区域,指向当前所执行的字节码.如果线程正在执行一个Java方法,这个计数器记录正在执行的虚拟机字节码指令的地址,如果执行的是Native方法,这个计算器值为空. Java虚拟机栈:线程私有的,其生命周期和线程一致,每个方法执行时都会创建一个栈帧用于存储局部变量表.操作数栈.动态链接.方法出口等信息. 本地方法栈:与虚拟机栈功能类似,只不过虚拟机栈为虚拟机执行J

java内存模型一

Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓性的一面,有时候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一个概念就是内存模型.究竟什么是内存模型?内存模型描述了程序中各个变量(实例域.静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,对象最终是存储在内存里面的,这点没有错,但是编译器.运行库.处理器或者系统

Java内存模型(JMM)

1. 概述 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标比较能说明问题,它代表着一秒内服务器平均能响应的请求数,而TPS值与程序的并发能力有着非常密切的关系.在讨论Java内存模型和线程之前,先简单介绍一下硬件的效率与一致性. 2.硬件的效率与一致性 由于计算机的存储设备与处理器的运算能力之间有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理

JMM java内存模型

JMM对于一个想要深入了解java的程序猿来说是不可避免的一关,本文偏理论性,尽可能说的通俗易懂,如有不对的地方希望多多指正. 那我们先说一下jvm的主内存分配 1 java虚拟机栈(java virtual stack) 虚拟机栈是线程私有的,每个线程都有一个自己的虚拟机栈,是java方法执行的内存模型,每个方法执行的时候都会在虚拟机栈上创建一个栈帧,栈帧是一个数据结构,主要存储的是方法中的局部变量(基本类型,对象的引用,returnAddress类型(指向一条字节码指令的地址)),操作栈(指

(转)JAVA内存模型

(原本准备把内存模型单独放到某一篇文章的某个章节里面讲解,后来查阅了国外很多文档才发现其实JVM内存模型的内容还蛮多的,所以直接作为一个章节的基础知识来讲解,可能该章节概念的东西比较多.一个开发Java的开发者,一旦了解了JVM内存模型就能够更加深入地了解该语言的语言特性,可能这个章节更多的是概念,没有太多代码实例,所以希望读者谅解,有什么笔误来Email告知:[email protected],本文尽量涵盖所有Java语言可以碰到的和内存相关的内容,同样也会提到一些和内存相关的计算机语言的一些

java内存模型及分块

转自:http://www.cnblogs.com/BangQ/p/4045954.html 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏 1.JMM简介 i.内存模型概述 Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓性的一面,有时候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一个概念就是内存模型.究竟什么是内存模型?内存模型描述了程序中各个变

再有人问你Java内存模型是什么,就把这篇文章发给他。

前几天,发了一篇文章,介绍了一下JVM内存结构.Java内存模型以及Java对象模型之间的区别.有很多小伙伴反馈希望可以深入的讲解下每个知识点.Java内存模型,是这三个知识点当中最晦涩难懂的一个,而且涉及到很多背景知识和相关知识. 网上有很多关于Java内存模型的文章,在<深入理解Java虚拟机>和<Java并发编程的艺术>等书中也都有关于这个知识点的介绍.但是,很多人读完之后还是搞不清楚,甚至有的人说自己更懵了.本文,就来整体的介绍一下Java内存模型,目的很简单,让你读完本文