首先了解下:计算机内存访问操作以及硬件的缓存访问操作之间的交互关系:(详细内容不在此叙述)
Java内存模型:主内存、工作内存、线程三者的交互关系:
内存间的交互操作:一个变量如何从主内存拷贝到工作内存?如何从工作内存同步回主内存?
lock锁定操作:作用于主内存的变量,把一个变量锁定(变量标识为一条线程独占的状态)。
unlock解锁操作:作用于主内存的变量,把一个锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read读取操作:作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load载入操作:作用于工作内存的变量,把read操作从主内存得到的变量值放入工作内存的变量副本中。
use使用操作:作用于工作内存的变量,把工作内存的变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign赋值操作:作用于工作内存的变量,把从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个变量赋值的字节码指令时执行这个操作。
store存储操作:作用于工作内存的变量,把工作内存中的变量值传送到主内存,以便随后的write操作使用。
write写入操作:作用于主内存的变量,把store操作从工作内存中得到的变量值放入主内存的变量中。
⚠️⚠️⚠️:上述的操作必须是顺序执行的,但不一定是连续执行的。也就是说read和load操作、store和write操作之间是可以插入其他指令的。
关键字volatile:java虚拟机提供的最轻量级的同步机制。
关键字volatile的作用:当一个变量被定义成volatile之后,那么它具备2项特性:
保证此变量对所有线程的可见性。“可见性”是指当一个线程修改了这个变量的值,新值对于其他线程来说是可以立即感知的。而普通变量做不到这一点,普通变量在线程之间进行传递时需要主内存来完成。(线程A修改一个普通变量的值,然后向主内存回写,线程B在线程A回写完成之后再对主内存进行读取,新变量的值才会对线程B可见。)
可见性:一共有3个关键字都可以保证:
volatile 当某个线程修改变量时能够立即同步到主内存,并使得其他线程使用时强制从主内存刷新该值;
synchronized 对一个变量执行 unlock 操作之前可以先把此变量同步回主内存中;
被 final 修饰的字段在构造器中一旦初始化完成且构造器没有把 this 的引用传递出去,就可以在其他线程中就能看见 final 字段的值。
禁止指令重排序优化。保证代码的执行顺序和程序顺序相同。
volatile插入内存屏障指令防止指令重排:
即读屏障:在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。
即写屏障:在写指令之后插入写屏障,能够让写缓存的数据写回到主内存中,以保证写的数据立刻对其他线程可见。
即全能屏障:具备即读屏障和即写屏障的能力。
Lock前缀:Lock不是一种内存屏障,但它能够完成类似全能型屏障的功能。
synchronized保证一个变量同一时刻只能有一个线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行的进入。