内存模型
2019-10-08 11:25:51 2 举报
AI智能生成
Java内存模型
作者其他创作
大纲/内容
重排序
数据依赖性
如果两个操作访问同一个变量,且这两个操作有一个为写操作,此时两个操作之间就存在数据依赖性
写后读
a=1;
b=a;
b=a;
写后写
a=1;
a=2;
a=2;
读后写
a=b;
b=1;
b=1;
as-if-serial
不管怎么排序,(单线程)程序的执行结果不能被改变
runtime和处理器共同为编写单线程的程序员创建一个幻觉:单线程程序是按顺序执行
程序顺序规则
JMM仅仅要求前一个操作(执行结果)对后一个操作可见,且前一个操作顺序排在第二个操作之前
重排操作后的结果与happens-before结果一致,不非法,允许重排
软件和硬件共同目标,尽可能开发并发度
重排序对多线程的影响
volatile
把对volatile变量的单个读/写,看成使用同一个锁对这些单个读/写操作做了同步
volatile变量自身特性
可见性。对于一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入
原子性:对任意volatile变量的读/写具有原子性,但volatie++ 这种复合操作就不具有原子性
建立的happens before关系
JSR-133后,volatile变量的写-读可以实现线程之间的通信
volatile的写-读与锁的释放-获取有相同的内存效果
volatile的写和锁的释放有相同的内存语义
volatile的读与锁的获取有相同的内存语义
volatile的写-读的内存语义
写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量值刷新到主内存
读一个volatile变量时,JMM会把线程对应的本地内存置为无效
总结
线程A写一个volatile变量,实质是向读这个volatile变量的某个线程发出消息
线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的消息
线程A写一个volatile变量,随后线程B读这个volatile变量,实质是线程A通过主内存向线程B发送消息
volatile的内存语义的实现
JSR-133为什么要增强volatile的内存语义
为了提供一种比锁更轻量级的线程之间的通信机制
volatile仅仅保证对单个volatile变量的读/写具有原子性
锁的互斥执行的特性可以确保对整个临界区代码的执行有原子性
concurrent包的实现
Java线程之间的通信
1.A线程写volatile变量,B线程读volatile变量
2.A线程写volatile变量,B线程用CAS更新这个volatile变量
3.A线程用CAS更新一个volatil变量,B用CAS更新这个volatile变量
4.A线程用CAS更新一个volatile变量,B线程读这个volatile变量
volatile变量的读/写 和 CAS可以实现线程之间的通信是concurrent包实现的基石
通用实现模式
1.声明共享变量为volatile
2.使用CAS的原子条件更新来实现线程之间的同步
3.配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信
concurrent包实现
总结
处理器内存模型
JMM的设计
两个关键因素
程序员对内存模型的使用
编译器和处理器对内存模型的实现
JMM向程序员提供的happens-befroe尽量满足程序员的需求
JMM对编译器和处理器的束缚已经尽可能少
JMM的内存可见性保证
单线程程序:不会出现内存可见性问题
编译器,runtime和处理器会共同确保单线程程序的执行结果与顺序一致性模型中结果一致
正确同步的多线程程序
正确同步的多线程程序具有与顺序一致性一样的执行结果
未同步/未正确同步的多线程程序
最小安全性保障:要么是执行时读取的值,要么是某个线程写入的值,要么是默认值(0,null,false)
JSR-133对旧内存模型的修补
增加volatile的内存语义
增加final的内存语义
基础
并发编程模型的分类
并发需要处理的问题
线程之间如何通信
线程之间如何同步
通信是指线程之间以何种机制类交换信息
共享内存
消息传递
两种并发模型
共享内存
线程之间共享程序的公告状态
线程之间通过写-读内存的公共状态来隐式通信
消息传递
线程之间没有公共状态
线程之间必须通过明确的发送消息来显式通信
Java
Java内存模型的抽象
重排序
为了提高性能,编译器和处理器对指令做重排序
1.编译器优化的重排序。不改变单线程程序语义下,重排
2.指令级并行重排,ILP,不存在数据依赖性,处理器可以执行顺序
3.内存系统的重排序。使用缓存和读/写缓冲区,加载和存储想乱序执行
处理器重排序与内存屏障指令
happens-before
JSR-133内存模型
规则
程序顺序规则
一个线程中的每个操作,happens-before与该线程中的任意后续操作
监视器锁规则
对一个监视器的解锁,happens-before于随后这个监视器的加锁
volatile变量规则
对一个volatile域的写,happens-before 于任意后续对这个volatile的读
传递性
A h-b B ,且 B h-b C,那么 A h-b C
顺序一致性
数据竞争与顺序一致性
数据竞争定义
在一个线程中写一个变量
在另一个线程读同一个变量
而且写和读没有通过同步来排序
如果程序是正确同步的,程序的执行将具有顺序一致性
即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同
顺序一致性内存模型
一个线程中的所有操作必须按照程序的顺序执行
所有线程都只能看到一个单一的操作顺序。在顺序一致性内存模型中,每个操作都必须执行且立刻对所有线程可见
同步程序的顺序一致性效果
未同步程序的执行特性
JMM只提供最小安全性
读的值要么是之前某线程写入的值
要么是默认值(0,null,false)
JVM在堆上分配对象时,清零内存空间同时分配对象
未同步程序在两个模型中的差异
1.顺序一致性模型保证单线程内按程序的顺序执行,JMM不保证单线程内的操作会按程序执行
2.顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不保证所有线程能看到一致的操作执行顺序
3.JMM不保证对64位long型和double型变量的读/写操作具有原子性,而顺序一致性模型保证
锁
锁的释放-获取建立的happen before 关系
锁释放和获取的内存语义
锁释放与volatile写有相同的内存语义
锁获取与volatile读有相同的内存语义
总结
线程A释放一个锁,实质是向要获取锁的某个线程发出消息
线程B获取一个锁,实质上是线程B接收了之前某个线程发出的消息
线程A释放锁,随后线程B获取锁,实质是线程A通过主内存向线程B发送消息
锁内存语义的实现
利用volatile变量的写-读所具有的内存语义
利用CAS所附带的volatile读和volatile写的内存语义
Final
编译器和处理器遵守两个重排序规则
1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序
2.初次读一个包含final域的对象引用,与随后初次读这个final域,这两个操作之间不能重排序
写final域的重排序规则
JMM禁止编译器把final域的写重排序到构造函数之外
编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障,这个屏障禁止处理器把final域的重排序到构造函数之外
在对象引用为任意线程可见之前,对象的final域已经被正确的初始化过,普通域不会有这个保障
读final的重排序规则
在读一个对象的final域之前,一定会先读包含这个final域的对象的引用
final语义在处理器中的实现
JSR-133为什么要增加final的语义
以前的模型里面final的值会变
只要对象是正确构造就不需担心初始化问题
0 条评论
下一页