- 在1、高速缓存与硬件基础中介绍了数据一致性问题的产生原因及其硬件层面的解决方案。可知:高速缓存的引入会产生数据一致性问题,通过缓存一致性协议可以解决这个一致性问题,但是会产生性能问题。因此引入了写缓冲器和无效化队列,虽然写缓冲器和无效化队列解决了性能问题,同时又会产生内存重排序和可见性问题,编译器等底层系统通过内存屏障的特殊指令解决这些问题。
- java虚拟机对sychronized、volatile、final关键字的语义实现也是借助于内存屏障的。
一、java同步机制与内存屏障
1、volatile关键字
- java虚拟机在volatile变量写操作之前插入的释放屏障使该屏障之前的任何读,写操作都优先于volatile变量写操作被提交,而java虚拟机在volatile变量读操作之后插入的获取屏障使的这个volatile变量读操作优先于该屏障之后的任何读、写操作被提交。写操作和读操作通过各自执行的释放屏障和获取屏障保证了有序性。
- 同时,java虚拟机 会在volatile变量写操作之后插入一个StoreLoad屏障。该屏障不仅禁止该屏障之后的任何读操作和该屏障之后的任何写操作之间的重排序,还充当着存储屏障和加载屏障的作用,保证了可见性(主要是消除写缓冲器和无效化队列产生的可见性问题)。
- x86处理器仅支持StoreLoad屏障,因此在x86处理器下面,java虚拟机会将其他的屏障指令映射为空,也就是在x86处理器下面,java虚拟机不需要再volatile读操作前后,写操作前插入任何指令,只是下写操作后插入一个StoreLoad屏障,在虚拟机中就会自行处理了。
2、sychronized关键字
- java虚拟机会在monitorenter(用于申请锁的字节码指令)对应的指令临界区开始之前的地方插入一个获取屏障。java虚拟机会在临界区结束后的monitorexit(用于释放锁的字节码指令)对应的指令前的地方插入一个释放屏障。获取屏障和释放屏障一起保证了临界区内的任何读写操作都无法被重排序到临界区外面,在加上锁的排他性,使的临界区内的操作具有原子性
- sychronized关键字实现可见性和有序性的原理和volatile关键字一致。也是通过获取屏障和释放配对使用实现的。
3、java虚拟机对内存屏障的优化
- 内存屏障部分禁止指令重排序的代价就是会阻止编译器、处理器做一些优化;另一种代价就是实现冲刷写缓冲区和清空无效化队列时候,比较耗时间。
- java虚拟机对内存屏障的时候会做一些优化,这些优化包括合并、省略等。
二、java内存模型
![](https://img.haomeiwen.com/i8458706/bf06259bf805e812.png)
- 为了解决高速缓存带来的一致性问题,产生了内存模型概念(内存一致性模型)。不同处理器架构有不同的内存模型,因为这些处理器对有序性的保证程度各异,表现在他们支持的内存重排序不同。为了屏蔽不同处理器的内存模型差异,以便java开发人员不必根据不同的处理器编写不同的代码,必须定义自己的内存模型,这个模型就是java内存模型。
- java内存模型:java内存模型是java语言规范的一部分,java内存模型定义了final、valotile、sychronized关键字的行为并确保正确同步java程序能够正确的运行在不同架构的处理器上,从应用开发角度讲,java内存模型作为一个模型,从“什么what”角度为我们解答了以下三个方面的线程安全问题:
原子性问题:针对实例变量、静态变量的读写操作,哪些具备原子性,哪些不具备原子性
可见性问题:一个线程的实例变量、静态变量在进程更新的时候,什么情况下面才能被其他线程所见
顺序型为题:
1、原子性
- java内存模型规定对long/double以外的任何基本类型和引用类型的共享变量进行读、写时候具有原子性;此外java内存模型规定对valotile关键字修饰的long/double型共享变量具有原子性
2、可见性和顺序型
- 对于可见性和顺序型,java内存模型使用happens-before来保证
假设动作A和动作B之间存在者happens-before关系,称之为A happens-before B,那么java内存模型保证A的操作结果对于B可见,及A的操作会在B被执行前提交(比如写入高速缓存或者主内存中)
- 从应用代码层次来看,可见性和顺序性的保证是通过应用代码中使用java线程同步机制实现的;只有正确的使用同步机制的两个动作之间才有happens-before关系,从而使可见性、有序性得以保证。
java内存模型定义的happens-before规则:
1、程序顺序规则:程序顺序规则意味着一个线程内任何一个工作的结果对程序顺序上该动作之后的其他动作是可见的。(但是如果A,B之间不存在数据依赖关系,JIT就有可能进行重排序,因此程序上A,B符合happens-before原则,但是并不是意味着时间处理上A必须优先于B)
2、内部锁规则:内部锁的释放happens-before后续每一个对该锁的申请lock(锁对排他性的保证仅局限于临界区的代码,但是锁对可见性和有序性的保证却可以扩展到临界区之前)
3、volatile变量规则:对一个volatile变量的写操作happens-before后续每一个正对该变量的读操作。
4、线程启动规则:调用一个线程的start方法happens-before被启动的线程中的任何一个动作。
5线程终止规则:
java标准库定义的happens-before规则:
![](https://img.haomeiwen.com/i8458706/af2335d388dc2743.png)
- java内存模型作为一个模型,它从“what(这里是happens-before原则)”来描述java语言对可见性和有序性的保证;但这些happens-before规则最终是由java虚拟机、编译器以及处理器一同写作落实的,而内存屏障是jvm、编译器和处理器之间的桥梁(也就是说需要从应用程序角度去实现happens-before原则)
3、总结
- 从语言(应用程序)的角度来说,内存模型提供的规则(原子性规则、happens-before规则)是通过java线程同步机制实现的;从底层的角度看,这些规则是通过jvm、编译器以及处理器一同协作实现的,而内存平展是jvm、编译器和虚拟机之间的“沟通”纽带。
- 根据java内存模型以及java标准类库定义的happend-before规则,我们便可以知道任意两个动作之间是否存在者happens-before关系,进而推导出两个线程所执行的操作之间的可见性和有序性保障。
- java内存模型和java标准类库提供了happens-before原则,用于变量在多线程之间的可见性和有序性问题,但是可见性和有序性并不能得到java虚拟机本省的保证。关键还是取决于应用程序的处理机制,只有正确的使用java线程同步机制,才能利用happens-before原则保证可见性和有序性问题。
网友评论