一、原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。及时在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。
像是a++ 并不是原子操作,因为a++其实包括,读取a的值,将a的值加1,将结果赋值给a
那么java内存模型中定义了8种原子方法,这8中方法也不能再拆分
1.lock(锁定)用于锁定主内存中变量,将他变成一个线程独占的状态
2.unlock(解锁)用于解锁变量被单线程独占的状态
3.read(读取),用于主内存中的变量,读取到工作内存中,以便load动作使用
4.load(加载),将read读取的内容放入变量副本中。
5.use(使用),将工作内存中的变量值,传递给执行引擎,
6.assign(赋值) 把从执行引擎传来的值,赋值给工作变量
7.store(存储) 将工作内存中的变量,传递给主存,以便于后面wirte的使用
8.write(写) 将它把store操作从工作内存中得到的变量的值放入主内存的变量中
注意:java内存模型只是保证他们每个操作是原子性的
由原子性变量操作read,load,use,assign,store,write,可以大致认为基本数据类型的访问读写具备原子性(例外就是long和double的非原子性协定)
将一个变量从主内存中放到线程内存中,需要使用read和load操作,线程内存中的值放入主内存中,需要使用store和write操作
其中六条可以满足基本数据类型的访问读写具备原子性,还剩下lock和unlock两条原子操作。如果我们需要更大范围的原子性操作就可以使用lock和unlock原子操作。尽管jvm没有把lock和unlock开放给我们使用,但jvm以更高层次的指令monitorenter和monitorexit指令开放给我们使用,反应到java代码中就是---synchronized关键字,也就是说synchronized满足原子性。
二、有序性
在单例模式的实现上有一种双重检验锁定的方式(Double-checked Locking),我们知道在创建对象时,其实是三步操作
instance = new Singleton();
1.为对象份分配内存空间,2.创建对象,3.对象指向内存空间
那么我们的jmm在满足happend-before规则的情况下,允许处理器进行指令重排序,也就是说在不影响结果的情况下,可以进行指令重排序, 1 2 步执行的先后顺序,对结果没有影响,因此可以发生指令重排序,当AB 线程方式访问时,可能A在执行了
instance = new Singleton(); 操作后,释放了锁,释放锁以后,先执行的如果是创建对象,那么B在获得锁以后,也会导致判断不为null的情况。
三、可见性
可见性是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。通过之前对synchronzed内存语义进行了分析,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。从而,synchronized具有可见性。同样的在volatile分析中,会通过在指令中添加lock指令,以实现内存可见性。因此, volatile具有可见性