Java内存模型、volatile关键字

Java内存模型

Java内存模型规定所有的变量都保存在主内存里,每个线程有自己的工作内存,每次对变量都操作都会将变量拷贝到工作内存,之后同步到主内存。
Java内存模型、volatile关键字

原子性、可见性、有序性

原子性:在Java中,对基本数据类型的读取和赋值是原子性操作,即不可拆分,要么一次执行,要么不执行
注意 ++操作不是原子操作
可见性:一个线程对某个变量的操作可以被其他线程立即可见,可见性需要主内存参与,因为线程每次都是从主内存取值。Java提供了volatile关键字满足这个特性,synchronized和Lock也可以满足,即同一时刻只有一个线程进入同步块,修改变量后退出同步块之前将当前值刷新到主内存。
有序性:Java内存模型允许编译器和处理器对指令重排序,这一点对单线程没有影响,但是会影响到多线程的正确执行,Java提供了volatile关键字通过禁止指令重排序满足有序性,synchronized和Lock也可以满足有序性。

Java有一些操作天然地满足有序性,称为"happens-before"原则(《深入理解Java虚拟机》)

  • 程序次序规则:一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:解锁操作先行发生于对同一锁的加锁操作
  • volatile规则:对一个volatile变量的写操作先行发生于对这变量的读操作。
  • 线程启动规则:Thread对象的start先行发生于该线程的其他操作。
  • 线程终止规则:一个线程里的所有操作先行发生于该线程的终止检测
  • 线程中断规则:线程的interrupt调用先行发生于被中断线程的代码检测到中断事件的发生
  • 对象终结规则:一个对象的初始化操作先行发生于该对象的finalize()的开始
  • 传递规则:A先行于B,B先行于C,则A先行于C。

volatile关键字

volatile的语义:

  • 一个线程对某个volatile变量的修改,对其他线程是立即可见的,即一个线程对volatile变量的修改要求强制刷新至主内存,而其他变量在读到volatile变量时强制从主内存读取
  • volatile禁止指令重排序。
    1)对volatile变量的读或写操作,保证其前面的操作已经完成,且结果对后面可见,对volatile变量操作后面的操作还没有开始
    2)指令优化时,保证不将对volatile变量访问的语句放到后面执行,保证volatile变量后面的操作放到其前面
  • volatile不保证原子性

volatile原理

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”–《深入理解Java虚拟机》
lock前缀指令相当于一个内存屏障,保证了以下几点:

  • 确保指令重排序时将内存屏障后面的指令放到其前面,也不会把前面的指令放到其后面
  • 强制将对缓存的修改刷新到主存
  • 如果是写操作,将导致其他CPU对应的缓存无效