java虚拟机(十)synchronized是如何实现可见性和有序性的,和volatile的区别

大家都知道 synchronized 是锁。那怎么会实现可见性和有序性。volatile也能实现对吧。

java内存模型是这么规定的

关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
  • read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中。

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现,以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
  • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。

可以看到最后一条synchronized 实现其实是靠lock和unlock实现的。在对一个变量unlock操作前。必须也先把此变量同步回主内存中。这就实现了可见性

而有序性是一个变量在同一个时刻只允许一个线程对其进行lock。这就说明了持有一个锁的两个同步块只能串行进行。

再来说下volatile。

他的可见性是在对变量进行use动作前必须在它前面进行load,read动作

在对变量进行assign动作后必须连续进行store,write动作。

也就是说,使用前先读取变量在内存的值,并且载入到工作内存副本中

赋值后要马上写回内存

而有序性是通过内存屏障来实现的。(重排序时不能把后面的指令重排序到内存屏障之前)

编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。(首先保证了正确性,再去追求执行效率)
1.在每个volatile写操作前插入StoreStore屏障;对于这样的语句Store1; StoreLoad; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
2.在每个volatile写操作后插入StoreLoad屏障;对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
3.在每个volatile读操作前插入LoadLoad屏障;对于这样的语句Load1;LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
4.在每个volatile读操作后插入LoadStore屏障;对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
如果编译器无法确定后面是否还会有volatile读或者写的时候,为了安全,编译器通常会在这里插入一个StoreLoad屏障
 

发布了82 篇原创文章 · 获赞 49 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/xu505928168/article/details/99292084
今日推荐