volatile和synchronized

volatile是Java提供的一种轻量级的同步机制,它在多处理器开发中保证了共享变量的可见性。如果volatile变量修饰符使用恰当的话,它比synchronized(synchronized通常称为重量级锁)的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

volatile实现原理

有volatile变量修饰的共享变量编译器生成的汇编指令会有Lock前缀。Lock前缀指令会引起处理器缓存回写到内存;一个处理器的缓存回写到内存会导致其他处理器的缓存无效(通过缓存一致性协议)。

缓存一致性协议(MESI):在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

内存屏障:一组处理器指令,用于实现对内存操作的顺序执行。

追加字节优化volatile,例如:LinkedTransferQueue。在缓存行非64字节宽的处理器、共享变量不会被频繁地写时不应该使用这种方式进行优化。

synchronized的实现原理

利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。

  1. 对于普通同步方法,锁是当前实例对象。
  2. 对于静态同步方法,锁是当前类的Class对象。
  3. 对于同步方法块,锁是Synchonized括号里配置的对象。

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

posted @ 2021-03-12 15:20  wpf2018  阅读(68)  评论(0编辑  收藏  举报