三大性质总结:原子性、可见性以及有序性


一. 原子性

原子性指的是一个操作是不可中断的,要么全部执行成功要么全部执行失败。

int a = 10;  //1
a++;           //2
int b = a;    //3
a = a + 1;   //4

以上四句代码中,只有1是原子操作。

JMM中定义了8种原子操作,是不可再分的。

  1. lock(锁定):作用于主内存中的变量,它把一个变量标识为一个线程独占的状态;
  2. unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便后面的load动作使用;
  4. load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本
  5. use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
  6. assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;
  7. store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用;
  8. write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

java内存模型只是要求上述两个操作是顺序执行的并不是连续执行的。也就是说read和load之间可以插入其他指令,store和writer可以插入其他指令。比如对主内存中的a,b进行访问就可以出现这样的操作顺序:read a,read b, load b,load a

可以大致认为基本数据类型的访问读写具有原子性。

volatile并不能保证原子性

如果让volatile保证原子性,必须符合以下两条规则:

  1. 运算结果并不依赖于变量的当前值,或者能够确保只有一个线程修改变量的值;
  2. 变量不需要与其他的状态变量共同参与不变约束

二.  有序性

synchronized语义要求线程在访问读写共享变量时只能串行执行,因此sychronized具有有序性

volatile包含禁止指令重排序的语义,其具有有序性

三.  可见性

可见性是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。

通过之前对sychronized内存语义进行了分析,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。从而,synchronized具有可见性

同样的在对于volatile,会通过在指令中添加lock指令,以实现内存可见性。因此, volatile具有可见性.

四.   final的内存语序

  1. 类变量:必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值,而且只能在这两个地方之一进行指定;
  2. 实例变量:必要要在非静态初始化块声明该实例变量或者在构造器中指定初始值,而且只能在这三个地方进行指定。

4.1    final域为基本类型:

写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域就不具有这个保障

读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读这个包含这个final域的对象的引用。

4.2    final域为引用类型:

额外增加约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量 重排序

final引用不能从构造函数中“溢出”!