状态 | 说明 |
---|---|
M(修改,Modified) | 本地处理器已经修改缓存行, 即是脏行, 它的内容与内存中的内容不一样. 并且此 cache 只有本地一个拷贝(专有)。 |
E(专有,Exclusive) | 缓存行内容和内存中的一样, 而且其它处理器都没有这行数据。 |
S(共享,Shared) | 缓存行内容和内存中的一样, 有可能其它处理器也存在此缓存行的拷贝。 |
I(无效,Invalid) | 缓存行失效, 不能使用。 |
数据依赖性
as-if-serial
不优化时的执行过程 | 优化时的执行过程 |
---|---|
指令获取。 | 指令获取。 |
如果输入的运算对象是可以获取的(比如已经存在于寄存器中),这条指令会被发送到合适的功能单元。如果一个或者更多的运算对象在当前的时钟周期中是不可获取的(通常需要从主内存获取),处理器会开始等待直到它们是可以获取的。 | 指令被发送到一个指令序列(也称执行缓冲区或者保留站)中。 |
指令在合适的功能单元中被执行。 | 指令将在序列中等待,直到它的数据运算对象是可以获取的。然后,指令被允许在先进入的、旧的指令之前离开序列缓冲区。(此处表现为乱序) |
功能单元将运算结果写回寄存器。 | 指令被分配给一个合适的功能单元并由之执行。 |
结果被放到一个序列中。 | |
仅当所有在该指令之前的指令都将他们的结果写入寄存器后,这条指令的结果才会被写入寄存器中。(重整乱序结果) |
通信
同步
同步是指程序用于控制不同线程之间操作发生相对顺序的机制。
Java 的并发采用的是 共享内存模型,线程之间的通信对程序员完全透明。
一会是编译器重排序一会是处理器重排序,如果让程序员再去了解这些底层的实现以及具体规则,那么程序员的负担就太重了,严重影响了并发编程的效率。因此,JMM 为程序员在上层提供了 8 个规则,这样我们就可以根据规则去推论跨线程的内存可见性问题,而不用再去理解底层重排序的规则。
从 jdk5 开始,Java 使用新的 JSR-133 内存模型,基于 happens-before 的概念来阐述操作之间的内存可见性。
在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
JMM 的设计示意图。
JMM 可以通过 happens-before 关系向程序员提供跨线程的内存可见性保证(如果线程 A 的写操作 a 与 B 线程的读操作 b 之间存在 happens-before 关系,尽管 a 操作和 b 操作在不同的线程中执行,但 JMM 向程序员保证 a 操作将对 b 操作可见)。
具体的定义为。
happens-before 的具体规则如下。
规则 | 说明 |
---|---|
程序次序规则 | 一个线程内,按照代码顺序,书写在前面的操作 happens-before 书写在后面的操作。 |
锁定规则 | 一个 unLock 操作 happens-before 后面对同一个锁的 lock 操作。 |
volatile 变量规则 | 对一个变量的写操作 happens-before 后面对这个变量的读操作。 |
传递规则 | 如果操作 A happens-before 操作 B,而操作 B 又 happens-before 操作 C,则可以得出操作 A happens-before 操作 C。 |
线程启动规则 | Thread 对象的 start() 方法 happens-before 此线程的每个一个动作。 |
线程中断规则 | 对线程 interrupt() 方法的调用 happens-before 被中断线程的代码检测到中断事件的发生。 |
线程终结规则 | 线程中所有的操作都 happens-before 线程的终止检测,我们可以通过 Thread.join() 方法结束、Thread.isAlive() 的返回值手段检测到线程已经终止执行。 |
对象终结规则 | 一个对象的初始化完成 happens-before 它的 finalize() 方法的开始。 |
操作 | 作用 |
---|---|
lock (锁定) | 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。 |
unlock (解锁) | 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 |
read (读取) | 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。 |
load (载入) | 作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。 |
use (使用) | 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时就会执行这个操作。 |
assign (赋值) | 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 |
store (存储) | 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后 write 操作使用。 |
write (写入) | 作用于主内存的变量,它把 Store 操作从工作内存中得到的变量的值放入主内存的变量中。 |
原子性
可见性
有序性
有序性规则表现在以下两种场景。
Java 内存模型的一系列运行规则,都是围绕原子性、可见性、有序性特征建立。是为了实现共享变量的在多个线程的工作内存的数据一致性,多线程并发,指令重排序优化的环境中程序能如预期运行。
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad 屏障 | Load1; LoadLoad; Load2 | 在 Load2 及后续读取操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕。 |
StoreStore 屏障 | Store1; StoreStore; Store2 | 在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。 |
LoadStore 屏障 | Load1; LoadStore; Store2 | 在 Store2 及后续写入操作被执行前,保证 Load1 要读取的数据被读取完毕。 |
StoreLoad 屏障 | Store1; StoreLoad; Load2 | 在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。它的开销是四种屏障中最大的(冲刷写缓冲器,清空无效化队列)。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。 |
Store Barrier
Load Barrier
Full Barrier
规则 | 说明 |
---|---|
规则 1 | 如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行 read 和 load 操作,如果把变量从工作内存中同步回主内存中,就要按顺序的执行 store 和 write 操作。但 Java 内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。 |
规则 2 | 不允许 read 和 load、store 和 write 操作之一单独出现。 |
规则 3 | 不允许一个线程丢弃它的最近 assign 的操作,即变量在工作内存中改变了之后必须同步到主内存中。 |
规则 4 | 不允许一个线程无原因的(没有发生过任何 assign 操作)把数据从工作内存同步回主内存中。 |
规则 5 | 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load 或 assign )的变量。即对一个变量实施 use 和 store 操作之前,必须先执行过了 load 或 assign 操作。 |
规则 6 | 一个变量在同一个时刻只允许一条线程对其进行 lock 操作,但 lock 操作可以被同一条线程重复执行多次,多次执行 lock 后,只有执行相同次数的 unlock 操作,变量才会被解锁。所以 lock 和 unlock 必须成对出现。 |
规则 7 | 如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行 load 或 assign 操作初始化变量的值。 |
规则 8 | 如果一个变量事先没有被 lock 操作锁定,则不允许对它执行 unlock 操作;也不允许去 unlock 一个被其他线程锁定的变量。 |
规则 9 | 对一个变量执行 unlock 操作之前,必须先把此变量同步到主内存中(执行 store 和 write 操作)。 |
保证可见性
禁止进行指令重排序
不能确保原子性
final 域的重排序规则
写 final 域的重排序规则
读 final 域的重排序规则
初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序。编译器会在读 final 域操作的前面插入一个 LoadLoad 屏障。
当构造函数结束时,final 类型的值是被保证其他线程访问该对象时,它们的值是可见的。
final 类型的成员变量的值,包括那些用 final 引用指向的 collections 的对象,是读线程安全而无需使用 synchronized 的。
public class MyClass { private final int myField = 1; public MyClass() { ... } }
或者
public class MyClass { private final int myField; public MyClass() { ... myField = 1; ... } }
下面的方法仍然可以修改该 list。
private final List myList = new ArrayList(); myList.add("Hello");
声明为 final 可以保证如下操作不合法
myList = new ArrayList(); myList = someOtherList;
锁释放和获取的内存语义。
concurrent 包的源代码实现通用化的实现模式。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic 包中的类),这些 concurrent 包中的基础类都是使用这种模式来实现的,而 concurrent 包中的高层类又是依赖于这些基础类来实现的。
内存模型名称 | 对应的处理器 | Store-Load 重排序 | Store-Store重排序 | Load-Load 和Load-Store重排序 | 可以更早读取到其它处理器的写 | 可以更早读取到当前处理器的写 |
---|---|---|---|---|---|---|
TSO | sparc-TSO | X64 | Y | Y | ||
PSO | sparc-PSO | Y | Y | Y | ||
RMO | ia64 | Y | Y | Y | Y | |
PowerPC | PowerPC | Y | Y | Y | Y | Y |
CPU 的三级缓存
缓存行
int[] arr = new int[64 * 1024 * 1024]; long start = System.nanoTime(); for (int i = 0; i < arr.length; i++) { arr[i] *= 3; } System.out.println(System.nanoTime() - start); long start2 = System.nanoTime(); for (int i = 0; i < arr.length; i += 16) { arr[i] *= 3; } System.out.println(System.nanoTime() - start2);
缓存关联方式(Associativity)
伪共享问题
伪共享的传统解决方案
public final class Test implements Runnable { private final static int num = 4; private final static long arrayValue = 500L * 1000L * 1000L; private final int arrayIndex; private static VolatileLong[] longs = new VolatileLong[num]; static { for (int i = 0; i < longs.length; i++) { longs[i] = new VolatileLong(); } } public Test(int arrayIndex) { this.arrayIndex = arrayIndex; } public static void main(String[] arg) throws InterruptedException { final long start = System.nanoTime(); runTest(); System.out.println("duration = " + (System.nanoTime() - start)); } private static void runTest() throws InterruptedException { Thread[] threads = new Thread[num]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new Test(i)); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } @Override public void run() { long i = arrayValue; while (0 != --i) { longs[arrayIndex].value = i; } } public final static class VolatileLong { public volatile long value = 0L; public long p1, p2, p3, p4, p5, p6; } }
16526073665
public final static class VolatileLong { public volatile long value = 0L; // public long p1, p2, p3, p4, p5, p6; }
31183015124
Java 8 中的解决方案
@sun.misc.Contended public final static class VolatileLong { public volatile long value = 0L; // public long p1, p2, p3, p4, p5, p6; }
https://www.jianshu.com/p/64240319ed60
https://www.jianshu.com/p/d3fda02d4cae
https://www.jianshu.com/p/d52fea0d6ba5
https://blog.csdn.net/suifeng3051/article/details/52611310
http://www.cnblogs.com/zhiji6/p/10037690.html
https://blog.csdn.net/hanmindaxiongdi/article/details/81159314
作者:羽杰
链接:https://www.jianshu.com/p/1e82c75034b7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原文:https://www.cnblogs.com/deepminer/p/12257645.html