Java代码编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
2.1
volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的”可见性“。可见性的意思是当一个线程修改一个共享变量时,另一个线程能够读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文切换和调度。
1. volatile的定义和实现原理
Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致性地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明为volatile,Java内存线程模型确保所有线程看到的变量的值是一致的。
volatile是如何保证可见性的尼?让我们在X86处理器下通过工具获取JIT编译器生成的汇编指令来查看volatile进行写操作时,CPU会做什么事情。
Java代码如下:
instance = new Singleton(); //instance是volatile变量
转变成汇编代码,如下:
0x01a3de1d: movb $0x0,0*1104800(%esi);
0x01a3de24: lock add1 $0*0,(%esp);
有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,Lock前缀的指令在多核处理器下回引发:
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了改地址的数据无效。
volatile的两条实现原则:
1)Lock前缀指令会引起处理器缓存回写到内存。
2)一个处理器的缓存回写到内存导致其他处理器的缓存无效
2. volatile的使用优化
通过追加字节提高并发编程的效率,以下两种场景不应该使用这种方式:
1)缓存行非64节宽的处理器。如P6和 奔腾处理器,它们的L1和L2高速缓存行是32个字节宽
2)共享变量不会频繁地写。因为追加字节的方式需要处理器的去更多的字节到高速缓冲区,这本身就会带来一定的性能消耗,如果不被频繁写的话,锁的几率也非常小,就没必要通过追加字节的方式来避免互相锁定。
2.2 synchronized的实现原理和应用
Java SE 1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。
synchronized实现同步的基础:Java中每一个对象都可以作为锁,具体表现为以下3种形式:
1)对于普通同步方法,锁是当前实例对象。
2)对于静态同步方法,锁是当前类的Class对象。
3)对于同步方法块,锁是synchronized括号里配置的对象
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。