volatile
其主要目的就是在多处理器开发中,保证共享变量的“可见性”,所谓“可见性”就是当一个线程修改了被volatile修饰的变量之后,其他线程可以读到这个修改值。
原理:被volatile修饰的变量在经过编译后,汇编代码中会出现一个 lock 前缀指令,这个指令会引起两个操作
- lock 前缀指令会使当前处理器缓存写回内存;
- 一个处理器的缓存写回内存这个动作会使其他处理器的缓存变得无效。
对volatile变量进行的写操作,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在的缓存行的数据写回到系统内存。在多处理下,为了保证各个处理器的缓存是一致的,就睡实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己的缓存行对应的内存地址被修改,就会将当前处理器的缓存行内容设置为无效状态,当处理器要对这个数据进行修改操作时,会重新从系统内存中把数据读取到处理器缓存中。
cpu相关:为了提高处理速度,处理器不直接与内存进行通信,而是先将系统内存的数据读取到内部缓存(L1,L2或L3)后再进行操作。
缓冲行:cpu高速缓存中可以分配的最小存储单元,处理器填写缓存行时会加载整个缓存行。
synchronized
利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁,表现为以下三种形式:
- 对普通同步方法,锁是当前实例对象;
- 对静态同步方法,锁是当前类的Class对象;
- 对于同步方法块,锁是synchronized括号里配置的对象。
当一个线程试图访问同步访法块时,它首先必须获得锁,在退出或抛出异常时必须释放锁。
JVM是通过进入和退出Monitor对象来实现方法同步和代码块同步的。这里会出现两个成对出现的指令monitorenter和monitorexit指令,monitorenter指令是在编译后插入到同步代码块的起始位置,monitorexit指令插入到方法结束和异常处。任何对象都有一个monitor对象与之关联,当一个monitor被持有后,对象处于锁定状态。当一个线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获取对象的锁。
Java SE 1.6 之后为了减小获得所和释放锁带来的性能消耗引入了偏向锁和轻量级锁。锁的级别从低到高依次为:无锁状态、偏向锁、轻量级锁、重量级锁。
在Mark word中分别对应的锁标志位为:01(无锁,偏向锁标志位为0)、01(偏向锁,偏向锁标志位为1)、00(轻量级锁)、10(重量级锁)、11(GC标记)。
偏向锁
HotSpot的作者经过研究发现,大部分情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。由此引入了偏向锁。
当一个线程访问同步块并获取锁时,
轻量级锁