全站最帅😎
发布于 2021-08-17 / 943 阅读
0
0

并发编程 - Java内存模型

1. 何为 Java 内存模型?

现代计算机体系大部是采用的对称多处理器的体系架构。每个处理器均有独立的寄存器和缓存多cpu下缓存与主存不一致.jpg多个处理器可同时执行同一进程中的不同线程,这里称为处理器的乱序执行。在Java中,不同的线程可能访问同一个共享或共享变量,如果任由编译器或处理器对这些访问进行优化的话,很有可能出现无法想象的问题,这里称为编译器的重排序。除了处理器的乱序执行、编译器的重排序,还有内存系统的重排序。因此Java语言规范引入了Java内存模型,Java内存模型规范了JVM如何 按需禁用缓存编译优化 的方法,具体来说包含 volatilesynchronizedfinal 三个关键字,以及八项 Happens-Before 规则。

2. Happens-Before 规则

Happens-Before 的语义是一种因果关系。在现实世界里,如果 A 事件是导致 B 事件的起因,那么 A 事件一定是先于(Happens-Before)B 事件发生的,这个就是 Happens-Before 语义的现实理解,总结就是就是 前面一个操作的结果对后续操作是可见的

2.1 程序顺序性规则

这条规则是指在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作

2.2 volatile 变量规则

这条规则是指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作

2.3 传递性规则

这条规则是指如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。这里是JDK对volatile的增强,具体应该是将当前CPU在此之前的操作刷新到主存,并要求其他CPU从主存刷新缓存
传递性规则.jpg

2.4 管程锁定规则

对同一个锁的解锁 Happens-Before 于后续对这个锁的加锁

synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=10
  if (this.x < 12) {
  this.x = 12; 
  }  
} //此处自动解锁
2.5 线程启动规则

Thread对象的start()方法先行发生于此线程的每一个动作。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作

Thread B = new Thread(()->{
  // 主线程调用B.start()之前
  // 所有对共享变量的修改,此处皆可见
  // 此例中,var==77
});
// 此处对共享变量var修改
var = 77;
// 主线程启动子线程
B.start();
2.6 线程终止规则

线程中的所有操作都先行发生于对此线程的终止检测
例如主线程 A 中启动子线程 B,并调用B.join(),当子线程 B 完成后,主线程能够看到子线程对 共享变量 的操作。

Thread B = new Thread(()->{
  // 此处对共享变量var修改
  var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用B.join()之后皆可见
// 此例中,var==66
2.7 线程中断规则

对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生

2.8 对象终结规则

一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始

3. Java内存屏障的实现

通过内存屏障(memory barrier)禁止重排序的,即时编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令。对于编译器而言,内存屏障将限制它所能做的重排序优化。而对于处理器而言,内存屏障将会导致缓存的刷新操作。比如,对于volatile,编译器将在volatile字段的读写操作前后各插入一些内存屏障


评论