java内存模型

65 阅读3分钟

在多线程编程存在有三个问题,分别是CPU缓存导致的可见性问题,线程切换而导致的原子性问题,指令重排导致的有序性问题,但是这些技术都是为了提高代码运行的效率,所以为了多线程编程不会出现问题,并且保证代码的运行效率,java内存模型就出现了

java内存模型主要是依靠三个关键词加一个规则解决多线程的三个问题

一个规则指的是happens-before 概念:如果A happens-before B 前面一个操作的结果对后续操作是可见的。也就是说,happens-before约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定要遵守happens-before原则。

  • 程序的顺序性规则:一个线程中的每个动作都 happens-before 该线程中后续的每个动作 在同一个线程中
  • 监视器锁定规则:监听器的解锁动作 happens-before 后续对这个监听器的锁定动作 在不同的线程中
  • volatile 变量规则:对 volatile 字段的写入动作 happens-before 后续对这个字段的每个读取动作
  • 线程 start 规则:线程 start() 方法的执行 happens-before 一个启动线程内的任意动作
  • 线程 join 规则:一个线程内的所有动作 happens-before 任意其他线程在该线程 join() 成功返回之前
  • 传递性:如果 A happens-before B, 且 B happens-before C, 那么 A happens-before C

三个关键字

volatile - 保证可见性有序性

对于用volatile修饰的变量,在编译成机器指令时,会在写操作后面,加上一条特殊的指令:“lock addl #0x0, (%rsp)”,这条指令会将CPU对此变量的修改,立即写入内存,并通知其他CPU更新缓存数据。

通过内存屏障来实现有序性

对volatile修饰的变量执行写操作,Java内存模型只禁止位于其前面的读写操作与其进行重排序,位于其后面的读写操作可以与其进行指令重排序。

对volatile修饰的变量执行读操作,Java内存模型只禁止位于其后面的读写操作与其进行重排序,位于其前面的读写操作可以与其进行指令重排序。

synchronized - 保证可见性有序性; 通过管程(Monitor)保证一组动作的原子性

通过加锁的方式保证操作的原子性

synchronized 不保证同步块内的代码禁止重排序,因为它通过锁保证同一时刻只有一个线程访问同步块(或临界区),也就是说同步块的代码只需满足 as-if-serial 语义 - 只要单线程的执行结果不改变,可以进行重排序

在进入synchronized代码块前,线程会将主内存中变量读取至工作内存,在解锁时再将变量修改至主内存来保证可见性

final - 通过禁止在构造函数初始化给 final 字段赋值这两个动作的重排序,保证可见性