注:本文仅是本人对学习资料的总结,如有不对之处请大家指出
java内存模型
有图有真相首先我们来一张图
这就是一个简单的JMM(java内存模型)我们可以看到
- 1每个线程独享一块自己的本地内存区域,来存储本地变量
-
2线程共享主内存中的数据
接下来我们再来看看线程间是怎么进行通信的
如图所示,如果A线程和B线程通信的过程
image
如果A想要和B进行通信实际是A刷新主存中x的值,然后B线程区读取主内存中的值。这样的通信方式也被称为隐式通信,即A和B进行通信不是直接进行,而是通过更新C,然后B到C中获取信息
根据刚刚的通信原理我们可以和容易的想到:如果多线程共享变量我们会怎么样? - A和B都会分别从主存中拷贝x的值,然后在自己的工作区进行加一的操作,然后在更新主存中x的值,所以无论A先更新还是B先更新,最终主存中x的值只会加一。这也是我们常见的线程安全问题一种原因
JVM内部对java内存模型的实现
image如果所示,java内存模型把内存分为了两个区域:线程栈区和堆区然后我们来明晰三个概念
- 线程栈与线程栈之间是相互不可见的
- 所有原始数据类型的本地变量都会被放在各自的栈区
- 对象都被存储在堆中,无论是成员变量还是本地变量
- 对象的成员变量都会被存储在堆区
- Static类型的变量以及类本身的相关变量都会存储在堆区
image
堆中的对象可以被多线程共享,如果多个线程同时访问一个对象的同一个方法,他们可以同时访问他的成员变量,但对于本地变量会拷贝到自己的线程栈中
硬件内存架构
软件最终还是依托与硬件,所以要了解java内存模型究竟是怎么运作的我们还需要了解底层的硬件逻辑
image
如图所示
CPU与主存的关系,处理过后的数据会放到,cpu寄存器中,再更新到cpu缓存区(这里cpu的缓存可以是多级的,因为不是专门研究硬件就不讨论了),最后更新到主存中。更新的时间是个玄学问题##Java内存模型和硬件架构之间的桥接关系
大部分内容会放在主存区,小部分才会在cpu寄存器中
image
因为这个玄学问题会引发两点
- 共享对象之间的可见性问题
脏读 - 共享对象之间的竞争现象
脏写
共享对象的可见性
image来我们来假设这么一个场景线程1要更新数据,而线程二要读取更新后的数据。首先线程一更新了数据但是却没有立刻更新到主存中,而是首先更新到cpu缓存区中,这个时候线程二想要读取更新好的数据,就会去缓存中读取数据,这个时候读到的却是更新前的。这也是脏读的现象。在java中我们可以使用volatite关键字来解决这个问题,至于volatite关键字,volatite关键字保证了变量会直接刷新到主存中而不是在缓存区中,并且会直接读取主存中的数据。怎么实现线程间的可见性我们随后会讨论到
竞争现象
image来同理我们再来看看脏写的现象,假设A和B线程同时要更新
主存中的数据,那么A和B会分别读取变量到自己的cpu缓存区,然后进行+1操作,应为这个动作是并行的所以最后flush会让主存中的数据被刷新了两次,结果不是+2而只是加一
解决这个问题,java中提供了synchronized代码块,synchronized代码块保证了同一时间只有一个数据进入竞争区,并且所有变量都会从主存区读取,当线程退出代码的时候,直接刷新主存中的数据
支持JAVA内存模型的基础原理
指令重排序
在执行程序的时候,为了提高性能会对指令进行重新排序,java通过插入内存屏障(Memory Barrier)来静止特定类型的编译器重排序和处理去重排序,为内存的可见性提供了保证。让我们来认识一下三种指令重排序方式
- 编译器重排序 :编译器在不改变单线程语义的前提下会对指令进行重新排序
- 指令集并行的重排序:如果存在数据依赖,处理器可以改变语句的执行顺序
- 内存系统的重排序:处理器使用缓存和读写缓存区,这使得加载和存储看起来是乱序的
数据依赖
如果两个操作同时访问数据,而一个为写操作,此时数据与数据之间具有数据依赖性不会对其进行重排序
as-if-serial
不管怎么重排序,单线程下的执行结果都不能被改变,编译器,处理器都必须遵循语义
内存屏障(Memorry Barrier)
- 保证特定特定操作的执行顺序
- 影响某些数据的内存可见性
任何指令都不能和Memorry Barrier重排序
write-Barrier写屏障刷出所有在Barrier之前写出cache的数据写到主存中,所以write-Barrier后读到的数据都是最新的数据
Read-Barrier指令,强制读取主存中的数据
volitile关键字就是在数据更新后插入了一个write-Barrier在数据读取前插入了一个Read-Barrier保证了 - 一个线程写入变量a后,任何变量都会访问最新的值
- 在更新变量a之前的写入操作,对其他线程都是可见的
happen-before原则
与程序员密切相关的happen-before原则
- 1.程序的执行顺序原则:一个线程的每个操作,hapens-before于该线程中任意后续操作。
- 监听器锁规则:对一个锁的解锁操作,hapens-before于随后对起加锁操作(sychronized)
- volatile域原则:对一个volatile域的写操作,happens-before于任意线程的后续对这个volatile域的读操作
- 传递性原则:如果A happens-beforeB。并且B happens-before C那么A happens-before C。
注意:happens-before,规定的并不是操作执行顺序,而是前一个操作的结果对后一个操作是可见的,且前一个操作排在后一个操作之前
网友评论