注:本篇即作者加深记忆编写
1.先行发生原则-它是判断数据是否存在竞争、线程是否安全的主要依据。
2.重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。(多线程不安全,遵循as-if-serial语义)
3.理想参考模型,在设计的时候,处理器的内存模型和编程语言的内存模型都会以顺序一致性内训模型作为参考。
一、先行发生原则(happens-before)
如果Java内存模型中所有的有序性都仅仅靠volatile和synchronized来完成,那么有一些操作将会变得很繁琐,但是我们在编写Java并发代码的时候并没有感觉到这一点,这是因为Java语言中有一个“先行发生”的原则。
举一个不安全的简单例子,来讲解现行发生原则的重要性,如下图:
注:保证线程安全的方案有很多:互斥同步、非阻塞同步和无同步方案(以上方案后续篇章会详细介绍)
下面是Java内存模型下一些“天然的”现行发生关系,这些现行发生关系无须任何同步器协助就已经存在,可以在代码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来(即同步)的话,他们就没有顺序性保障虚拟机可以对他们随意地进行重排序。
(“后面”是统一指时间上的先后顺序。)
程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作现行发生与后面的操作。准确地 说,应该是控制流顺序,而不是程序代码顺序,因为要考虑分支、循环等结构。
管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁lock操作。这里必须强调的是同一个锁
volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
线程启动规则(Thread Start Rule):Thread对象的start()-优先当前Thread其他所有动作。
线程终止规则(Thread Termination Rule):终止坚持最后,通过Thread.join()方法结束、Thread.isAlive()检测是否中断。
对象中介规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行于它的finalize()方法的开始。
传递性(Transitivity):A->B ,B->C, A->C(->标示优先于)
二、指令重排序(多线程下可见性就会出现问,即不安全问题)
需要记住,在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型。
1)编译器优化重排序。编译器在不改变但线程程序语句的前提下,可以重新安排语句的执行顺序。
2)指令级并行的重排序(parallel)。现代处理器采用了指令集并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
源代码执行的时候会一次进行 以上三种的重排序,生成最终执行的指令序列。
JMM处理器重排序规则会要求Java编译器在生成指令序列时,插入特定的内存屏障(Memory Barriers,Intel称之为Memory Fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序。
as-if-serial语义:不管怎么重排序,程序的执行结果不能被改变。
三、顺序一致性
当程序未正确同步时,就可能会存在数据竞争。当数据竞争时,因为未正确同步,执行的结果往往违反直觉。
JMM对正确同步的多线程程序的内存一致性做了如下保证。
如果程序是正确同步的,程序的执行将具有顺序一致性(Sequentially Consistent)。即程序的执行结果与该程序的顺序一致性内存模型中的执行结果想通!!!!这个相当牛逼
(注:这里的同步是广义上的同步,互斥同步(synchronized、volatile和final)、非阻塞同步(cas。。。)等正确的使用)
顺序一致性内存模型特性
1)一个线程中所有操作必须按照程序的顺序来执行。
2)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作必须都是原子执行且立刻对所有线程可见。