内存屏障

JMM内存模型
在这里插入图片描述
线程A与线程B之间的通信,必须经历两步
1)线程A把本地内存A中更新过的共享变量刷新到主内存去
2)线程B到主内存中去读取线程A之前已更新过的共享变量
重排序
1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2)指令级并行的重排序。现代处理起采用了指令级并行技术静多条指令重叠执行。不过不存在数据依赖性,处理起可以改变语句对应机器指令的执行顺序
3)内存系统的重排序。由于处理器使用缓存和读/写缓存区,这使得加载和存储操作看上去可能是乱序执行。
1)属于编译器重排序,2)3)属于处理起重排序,这些重排序会导致多线程程序出现内存可见性问题。对于编译器,JMM编译器重排序规则会禁止特定类型的编译器重排序。对于处理起重排序,JMM的处理起重排序规则会要求java编译器在生成指令序列时,插入特定类型的内存屏障指令,来禁止特定类型的处理器重排序。

常见的处理器都允许Store-Load重排序,常见的处理起都不允许对存在数据依赖性的操作做重排序,为了保证内存可见性,java编译器在生成指令序列的适当位置会插入内存屏障来禁止特定类型的处理器重排序。
内存屏障类型
LoadLoad Barriers
happens-before
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么,这两个操作之间必须要存在happens-before关系,可以是一个线程之内,也可以是不同线程之间。
编译器和处理起在重排序时,会遵守数据依赖性,编译器和处理起不会改变存在数据依赖性关系的两个操作的执行顺序
数据依赖性只针对单个处理器中执行的指令序列和单个线程中执行的操作

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

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

数据依赖性
编译器和处理起在重排序时,不会改变存在数据依赖关系的两个操作的执行顺序,但是只这对单个处理器中执行的指令序列和单个线程中执行的操作,不针对多线程和多处理器

as-if-serial
它的意思是:不管怎么重排序,单线程程序的执行结果不能被改变

volatile语义
一个volatile变量的单个读/写操作,与一个普通变量的读/写操作都是使用同一个锁来同步,他们之间的执行效果相同。
volatile变量自身具有以下特性
可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
原子性:对任意单个volatile变量的读/写具有原子性,但i++这种复合操作不具有原子性

volatile的写和锁的释放有相同的内存语义,volatile读与锁的获取有相同的内存语义

volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
volatile读的内存语义:当读一个volatile变量时,JMM会把该程序对应的本地内存置为无效,线程将从主内存中读取共享变量。

JMM如何实现volatile写/读的内存语义
在这里插入图片描述
为了实现volatile内存语义,编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定烈性的处理起重排序。

保守策略的JMM内存屏障插入策略
在每个volatile写操作的前面插入一个StoreStore屏障
在每个volatile写操作的后面插入一个StoreLoad屏障
在每个volatile读操作的后面插入一个LoadLoad屏障
在每个volatile读操作的后面插入一个LoadLoad屏障

锁的内存语义
1.当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中
2.当线程释放锁,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量

final域的内存语义
final域的重排序规则
对于final域,编译器和处理器要遵守两个重排序规则
1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作不能重排序

写final域的重排序规则
禁止把final域的写重排序到构造函数之外,这个规则的实现包含2点
1)JMM禁止编译器把final域的写重排到构造函数之外
2)编译器会在final域的写之后,构造函数return之前,插入一个storestore屏障,禁止把final域的写重排序到构造函数之外
读final域的重排序规则
初次读一个包含final域的对象的引用,与随后初次读这个final域,JMM禁止处理器重排序这两个操作,编译器会在读final域的前面插入一个loadload屏障
这两个操作之间存在间接依赖关系,因此编译器不能重排序这两个操作

final域为引用类型
在构造器内对一个final引用的对象成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序

单例双重检查

public class Singleton{
	private static Instance instance;
	public static Instance getInstance() {
		if (instance == null) {
			synchronized(Singleton.class){
				if (instance == null) {
					instance = new Instance();	
				}
			}
		}
		return instance;
	}
}

instance = new Instance();分为3步
1.分配对象的内存空间
2,初始化对象
3,设置instance指向刚分配的内存地址
2和3可能法生重排序,单线程不会有问题,多线程会出现问题,当第二个线程来的时候,由于2,3重排序,导致instance不为空了,直接返回,但是第二步初始化没有完成

两种解决方案
1.不允许重排序

private volatile static Instance instance;

2.基于类初始化的解决方案

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值