内存模型
内存模型的基础
内存模型的抽象结构
共享变量 堆里面的对象、数组元素,静态变量(方法区)
局部变量 不会有线程可见性的问题,不会受内存模型的影响
指令重排
编译器优化的重排序 不改变单线程语义的前提下重新安排编译的顺序 --编译器
指令级并⾏的重排序 现在的处理器采用的技术,多条指令重叠进行,只要两条指令不存在数据依赖性 --处理器
内存系统的重排序 处理器会使用缓存或读写缓冲区的时候,程序运行的加载或存储这些操作看上去像是在乱序执行 -处理器
数据依赖性 --两个操作有同一个变量,且有一个操作是写操作
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
基于类初始化的解决⽅案 (对静态字段延迟初始化)
字段的延迟初始化降低了初始化类或者创建实例的开销,也增加了访问被延迟初始化的字段的开销,多数情况下,正常的初始化是由于延迟初始化的
枚举可以实现单例,但是不实用