内存模型

内存模型的基础

 

 

内存模型的抽象结构
共享变量  堆里面的对象、数组元素,静态变量(方法区)
局部变量 不会有线程可见性的问题,不会受内存模型的影响

 

指令重排
编译器优化的重排序   不改变单线程语义的前提下重新安排编译的顺序   --编译器
指令级并⾏的重排序   现在的处理器采用的技术,多条指令重叠进行,只要两条指令不存在数据依赖性 --处理器
内存系统的重排序   处理器会使用缓存或读写缓冲区的时候,程序运行的加载或存储这些操作看上去像是在乱序执行 -处理器

 

 

数据依赖性 --两个操作有同一个变量,且有一个操作是写操作

 

 

 

as-if-serial
定义:不管怎么重排序(编译器和处理器为了提⾼并⾏度),(单线程) 程序的执⾏结果不能被
改变。编译器、runtime和处理器都必须遵守as-if-serial语义。
 
happens-before
JMM设计意图
程序员对内存模型的使⽤   为程序员提供⾜够强的内存可⻅性保证
编译器和处理器对内存模型的实现   对编译器和处理器的限制要尽可能地放松
 
happens-before
在JMM中,如果⼀个操作执⾏的结果需要对另⼀个操作可⻅,那么这两个操作之间必须要存在happens-before关系。
程序顺序规则
监视器锁规则
volatile变量规则
⼀个线程中的每个操作,happens-before于该线程中的任意后续操作。
对⼀个锁的解锁,happens-before于随后对这个锁的加锁。
对⼀个volatile域的写,happens-before于任意后续对这个volatile域的读。
传递性 如果A happens-before B,且B happens-before C,那么A happens-before C。
start()规则
如果线程
A执⾏操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中
的任意操作。
如果线程
A执⾏操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()
操作成功返回。
对线程interrupt⽅法的调⽤happens-before于被中断线程的代码检测到中断事件的发⽣。
join()规则
线程中断规则
对象终结规则
⼀个对象的初始化的完成,也就是构造函数执⾏的结束⼀定 happens-before它的finalize()⽅法。
 

 

 asifserial  创造了一个幻觉:程序员在写单线程程序的代码时是按程序的顺序进行的

happens before在我们创建正确同步的多线程的代码时也创造了一个幻觉:正确同步的多线程程序是按照happens before规则来执行的

一个happens-before规则,可能对应着多个编译器处理器的规则

 

 

 

 

 

 每个处理器允许重排序的类型都不同

 

 

 

解决方法1 :在同一个对象的两个方法中加锁       

 

 

 

 

 

 

 

java里面任意对象都可以作为锁 ,一个对象用的是同一把锁

 moniter存在java对象的对象头里,spring管理的对象是单例的,每个对象的对象头都有一个moniter,用的是同一把锁

 

 

 

方法加锁是加标志位

 

 

 

锁的内存语义
线程A释放⼀个锁,实质上是线程A向接下来将要获取这个锁的某个线程
发出了(线程A 对共享变量所做修改的)消息。
线程B获取⼀个锁,实质上是线程B接收了之前某个线程发出的(在释放
这个锁之前对共 享变量所做修改的)消息。
线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主
内存向线程B发 送消息
volatile的内存语义

 

 

 

 

 

volatile的内存语义
在每个volatile写操作的前⾯插⼊⼀个StoreStore屏障
在每个volatile写操作的后⾯插⼊⼀个StoreLoad屏障
在每个volatile读操作的后⾯插⼊⼀个LoadLoad屏障
在每个volatile读操作的后⾯插⼊⼀个LoadStore屏障

 

 

 

 jmms首先会保证正确性,然后才会保证执行的效率,

在实际执行的时候,只要不改变volatile读和写的内存语义,编译器可以根据具体的情况省略不必要的屏障,比如在单线程的情况下,javap显示的字节码看不到内存屏障,可能直接优化掉了

 
 
Final的内存语义
在构造函数内对⼀个final域的写⼊,与随后把这个被构造对象的引⽤赋
值给⼀个引⽤ 变量,这两个操作之间不能重排序。
初次读⼀个包含final域的对象的引⽤,与随后初次读这个final域,这两
个操作之间不能 重排序

 

 

 

Final的内存语义

 

 

 

 

 

 

只要对象是正确构造的(被构造对象的引⽤在 构造函数中没有“逸出”),那么不需要使⽤
同步(指lock和volatile的使⽤)就可以保证任意线程 都能看到这个final域在构造函数中
被初始化之后的值。
 
多线程下的单例模式

 

 

 

 

不允许2和3重排序。
允许2和3重排序,但不允许其他线程“看到”这个重排序。
基于volatile的解决⽅案  除了对静态字段实现延迟初始化,还可以对实例字段实现延迟初始化   对实例字段进行线程安全的延迟初始化方法--volatile
基于类初始化的解决⽅案 (对静态字段延迟初始化)
 
字段的延迟初始化降低了初始化类或者创建实例的开销,也增加了访问被延迟初始化的字段的开销,多数情况下,正常的初始化是由于延迟初始化的

 

 

 

 枚举可以实现单例,但是不实用

 

 

 

 

 

 

 

 

 

posted @ 2021-06-22 23:12  悬崖听风098  阅读(100)  评论(0编辑  收藏  举报