jmm的认识与内存屏障的看法

什么是 jmm?

jmm,java内存模型;为了屏蔽不同系统及硬件的差异,java 虚拟机规范定义的线程并发时内存访问模型;
jmm从内存分配上,将内存虚拟的分为每个线程独有的工作线程,与共有的主线程;每个线程之间不能访问别的线程中的内存;从内存交互上定义了8种原子的 lock、unlock、read、load、use、assign、store、write 内存交互操作;
从内存访问安全特性上说;
1、原子性:对于简单的基本类型的赋值操作 比如 int i = 10,是原子操作;而对于 i = j ,则不是原子操作;同时使用 lock、unlock 来保证代码片段的原子性;java 关键字 synchronized 就是隐式的使用 lock/unlock 操作,synchronized 保证只能由一个线程访问 synchronized 锁住的代码,从而保证了原子性;

2、可见性:一个线程中对内存的修改操作,对别的线程可见;java 提供了 volatile 关键字实现了该功能;java 要求被 volatile 修饰的变量,在更新后立即同步到主内存,及对 volatile 修饰的变量 进行 assign 操作后应同时执行 store 与 write 操作,使得别的线程中对改变量的缓存数据失效;java 中的 synchronized 也可以实现可见性,是因为 java虚拟机 要求在执行 unlock 操作前需要执行 store 与 write 操作,将工作内存中的值同步到主内存中去;java 中被 final 修饰的实例变量,且在构造方法返回前不存在 this引用逃逸(多线程环境下,在对象构造方法返回之前,this引用被其余线程访问到)的时候,也是对其他线程可见的;java 实现 final实例变量的 可见性是在构造方法返回前添加 storestore 内存屏障;

	int i ;
	final int j;
	static volatile A a;
	
	private A(){
		// a = this; 这会导致 this 引用逃逸 
		i = 1;
		j = 2; 
	}
	
	public static  write(){
		if(a == null){
		 a = new A()
		}
	}
	
	pubic static read(){
		if(a != null ){
			int x = a.j;
			int y = a.i;
		}
	}
线程1 执行 write 时,在执行构造函数时,可能会将 i 重排序到构造函数之外;线程2执行时,会读取到非预期的i = 0 ;

3、有序性:乱序的原因是重排序,编译器与cpu在编译与执行指令时,为了提高cpu的运行效率,会优化指令的执行的顺序;在单线程时,这种优化是安全的,但是在多线程交替执行时,这种优化会导致部分代码没有达到预期的值;java的hapen-before的8个规则,能够保证的代码的执行先后顺序;其中volatile变量规则,java内存模型通过使用内存屏障进行保证;jmm 描述了4种内存屏障,分别是 loadload,storestore,loadstore,store;以最保守的策略保证 volatile 的语义,java虚拟机对 volatile 写指令之前,添加 storestore 指令,在写之后添加 storeload指令,前者保证常规的写操作在 volatile 写之前完成,后者保证 volatile 写指令优先与其后的读指令完成;
对 volatile 字段读之后,先条件一个 loadload 指令,保证在 volatile 读在之后的读之前完成,再添加一个 loadstore 指令,保证之后的写指令务必在 volatile 读取完毕后才执行;***(内存屏障知识是虚拟机需要面对不同的处理器进行的封装,具体在什么处理器上面对什么语境插入怎样的屏障,我觉得没有必要深入了解,尽管我尝试理解了很久,20个小时吧,也才得到浅显的认识,但是现在我认为这是没有必要的)***
以 jdk1.5之后的 dcl 为例

 private static volatile A singleton;
 private A(){}
 public static A getSingleton(){
	if(singleton == null){
		synchronized(A.class){
			if(singleton == null){
				singleton = new A()
				return singleton ;
			}
		}
	}
	return singleton ;
 }
在 jdk1.5之前,这一段代码是不安全的,因为之前的版本 volatile 字段不能保证其前后的代码的禁止重排序;常规情况下, singleton = new A()
可以分为 1、分配一个内存 2、执行构造方法 3、将内存地址赋值给引用 singleton ;
从 singleton 的写分析,如果可以重排序,则有可能执行 1 3 2,在执行 3 之后,线程切换到 判断 singleton != null;如果加上屏障,这里我们假设 3 是真实的 volatile 写指令,会在 3之前加上 storestore,保证2与1都优先与3执行;加上 storeload 指令,又保证了3执行后对所有的线程可见;(以上分析可能存在错误,纯属臆想,如有大佬愿意告诉我是错误的,我会看到后修改)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值