java内存模型

前言

在学习java多线程并发编程前,必须要了解java内存模型,只有了解java内存模型,才能知道为什么多线程并发时会出现数据不一致,什么时候需要加锁同步等各种问题。下面只是简单阐述下java内存模型及其相关的概念。

内存模型简介

java的并发采用的是共享内存模型(而非消息传递模型)。

Java内存模型(Java Memory Model)描述了Java程序中各种变量(共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:

内存模型

图中的共享变量为:实例变量和静态变量。(局部变量是线程私有的不存在竞争)

从图中看,线程A和线程B进行通讯必须通过主内存

  1. 线程A修改了一个共享变量,并刷新到主内存中
  2. 线程B从主内存读取A修改过的共享变量。

注意:

  1. 线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读写。
  2. 不同线程之间无法直接访问其它线程的本地内存,线程间的变量值的传递,必须通过主内存来完成。

指令重排序

指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。编译器、处理器也遵循这样一个目标。注意是单线程。多线程的情况下指令重排序就会给程序员带来问题。

重排序分三种类型:

Paste_Image.png

  1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

内存可见性

内存可见性简单描述:当主内存中的一个共享变量在多个线程的本地内存中都存在副本,如果一个线程修改共享变量,其它线程也应该能看到被修改后的值。

要实现共享变量的可见性,必须实现两点:

  1. 线程修改后的共享变量值能够及时的从工作内存刷新到主内存中。
  2. 其它线程能够及时把共享变量的最新值从主内存更新到自己的本地内存中。

happens-before

JMM使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作就必须存在happens-before关系。

happens-before规则如下:

  1. 程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
  2. 监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
  3. volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
  4. 传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。

注意,两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。

as-if-serial

as-if-serial定义重排序的规则。 as-if-serial语义的意思:不管怎么重排序,程序的执行结果都不能被改变。编译器、runtime和处理器都必须遵守as-if-serial规则。

例如:

int a=2;            //A
int b=3;            //B
int c=a*b;          //C

Paste_Image.png

注意:

  1. 重排序不会给单线程程序带来内存可见性问题
  2. 多线程程序并发交叉执行时,重排序可能会造成内存可见性问题

上面程序的happens-before的关系:

  1. A happens-before B
  2. B happens-before C
  3. A happens-before C (happens-before的传递性)

在这里A happens-before B, 但实际执行B可以排在A之前执行。JMM仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在第二个操作之前。这里A的执行结果不需要对操作B可见。


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏流柯技术学院

python 中文乱码问题

cnstr就是你的中文字符串,做一下判断:如果是unicode,直接转码,如果不是,先解码再转码(解码前要知道你的字符串是什么编码)。

29320
来自专栏happyJared

Linux私房菜:走进bash

减号-可用于连接一些特殊的stdin和stdout,像这个文件压缩和解压缩的示例:tar -cvf - /home | tar -xvf - -C /tmp/h...

9320
来自专栏大内老A

通过自定义ServiceHost实现对WCF的扩展[原理篇]

除了采用自定义特性声明(服务行为、契约行为和操作行为)或者配置的方式(服务行为和终结点行为)应用自定义的行为之外,我们还可以通过自定义ServiceHost来应...

19860
来自专栏Jimoer

JVM学习记录-线程安全与锁优化(一)

线程:程序流执行的最小单元。线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I...

8020
来自专栏python3

习题13:参数,解包,变量

在第三行,有一个"import"语句,这是你将python的功能引入你的脚本方法,python不会一下子将所有的功能都给你,而是让你需要什么就调用什么,这样可以...

11650
来自专栏逸鹏说道

C# 温故而知新: 线程篇(二) 上

线程池和异步线程 目录: 1 什么是CLR线程池? 2 简单介绍下线程池各个优点的实现细节 3 线程池ThreadPool的常用方法介绍 4 简单理解下异步线程...

32590
来自专栏安恒网络空间安全讲武堂

二进制学习系列-栈溢出之libc_init

这是一道ctf wiki上面的一道中级ROP,思路很明确,但是还是有些小坑,比如说write函数上面,还有pwntools函数上面等等…

40230
来自专栏chenssy

【死磕Java并发】—–Java内存模型之总结

经过四篇博客阐述,我相信各位对Java内存模型有了最基本认识了,下面LZ就做一个比较简单的总结。 总结 JMM规定了线程的工作内存和主内存的交互关系,以及线程之...

36880
来自专栏李家的小酒馆

Redis-Nosql数据库入门

简介 Redis是Nosql数据库的一种,可基于内存亦可持久化的日志型、是一个Key-Value数据库,多用在缓存方面 安装 Windows 下载地址, 最...

25900
来自专栏落影的专栏

静态库与动态库的思考

前言 在上文《编译与链接过程的思考》评论中暴走大牙提到了静态库和动态库依赖的问题,还在群里提了几个测试样例和测试工程。 大致介绍下测试工程和如何进行测试: ...

39260

扫码关注云+社区

领取腾讯云代金券