Java内存模型与happens-before原则

版权声明:如需转载,请附上原文链接即可 https://blog.csdn.net/doujinlong1/article/details/84838194

Java内存模型

Java内存模型不同于Jvm内存模型,Java内存模型(JMM)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将于其他线程可见。

在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM),JVM通过在适当的位置插入内存栅栏来屏蔽JMM和各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。也就是说,在java内存模型中,也会存在缓存一致性问题和指令重排序的问题。

Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
如图:
imageaf5e572be8aad7ac.png

一,重排序

再没有充分同步的程序中,如果调度器采用不恰当的方式来交替执行不同线程的操作,那么将导致不正确的结果。更糟糕的是,JMM还使得不同线程看到的操作执行顺序是不同的,从而导致在缺乏同步的情况下,要推断操作的执行顺序更加复杂,各种使得操作延迟或者砍死乱序执行的不同原因,都可以称为“重排序”

将缓存刷新到主内存的不同时序也可能会导致重排序。

重排序的基础是前后语句不存在依赖关系时,才有可能发生指令重排序。

内存栅栏会屏蔽重排序

两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)

二,happens-before

happens-before原则是指如果A和B满足这个happens-before原则,则可以保证操作B的线程可以看到操作A的结果。

当一个变量被多个线程读取并且至少被一个线程写入时,如果在读操作和写操作之间没有按照H-B原则来排序,就会产生数据竞争问题,在正确同步的程序中不存在数据竞争,并且会表现出串行一致性。

1,程序顺序原则
单线程中,写在前面的操作A会在写在后面的操作B之前执行。

2,锁原则
在锁上的解锁操作必须在锁上的加锁操作之前执行。

3,volatile变量原则
对volatile变量的写入操作必须在对该变量的读操作之前执行。

4,线程启动原则
在一个线程上,start操作必须在该线程执行任何操作之前执行。

5,线程关闭原则
线程中任何操作都必须在其他线程检测到该线程已经结束之前执行。

6,线程中断原则
当一个线程在另一个线程调用interrupt时,必须在被中断线程检测到interrupt调用之前执行。‘’

7,终结器规则。
对象的构造函数必须在启动该对象的终结器之前执行完成。

8,传递性
如果操作A H-B 操作B,操作B H-B 操作C 则 操作A H-B 操作C

如果2个在不同线程的操作不满足H-B原则,则无法推断一个操作是否一定在另一个操作之前。

如:

image.png

这里,在多个线程来调用时,因为不满足H-B,所以第一个线程调用时,已经初始化了resource,没法保证第二个线程来时,获取到的resource到底是拿到的null还是一个失效值。

三,借助同步

由于H-B排序功能很强大,因此有时候可以“借助”现有同步机制的可见性属性。

“借助同步”技术是指借助于现有的H-B原则,来确保对象X的可见性,而不是为了发布X而创建一个H-B顺序。

在类库中提供的其他H-B顺序:
imagefee65956437ebe14.png

四,并发编程的三个问题

1,原子性

一个操作要么全部执行,要么不执行。

JAVA中原子性靠synchronized和Lock来实现,或者native方法的cas等

2,可见性

多个线程访问一个变量时,一个线程修改后其他线程可以立即看到这个值的改变。

Java提供了volatile关键字来保证可见性

3,有序性

程序执行的顺序按照代码的先后顺序执行。

Java中保证了单线程下看起来有序(as if serial),不保证多线程下有序。

因为在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

猜你喜欢

转载自blog.csdn.net/doujinlong1/article/details/84838194