volatile是轻量级的synchronize,它在多处理器开发中保证了共享变量的“可见性”,由于它不会引发线程上下文的切换和调度,因此比synchronize的使用和执行成本更底。
为了提升处理速度,处理器不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其余)后再进行操做,但操做完不知道什么时候会写到内存。使用volatile变量,在操做后,JVM会发出lock指令java
synchronize实现同步的基础,具体表现为三种形式数组
当一个线程试图访问同步代码块时,它首先必须获得锁,退出或抛出异常时必须释放锁。那么锁到底存在那里,锁里会存储什么信息。缓存
synchonize用的锁是存在java对象头里的。若是对象是数组类型,则JVM用三个字宽存储对象头,若是对象为非数组类型,则用二个字宽存储对象头。32位中,一字宽等于四字节(32bit)多线程
长度 | 内容 | 说明 |
32/64bit | Mark Word | 存储对象的hashCode或锁信息等。 |
32/64bit | Class Metadata Address | 存储到对象类型数据的指针 |
32/64bit | Array length | 数组的长度(若是当前对象是数组) |
在运行期间Mark Word里存存储的数据会随着锁标志位的变化而变化。会成为下面的一种并发
为了减小得到锁与释放锁所带来的性能消耗,引入“偏向锁”和“轻量级锁'.因此在java中存在四种状态性能
它会随着竞争状况逐渐升级。锁能够升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁优化
Hotspot的做者通过以往的研究发现大多数状况下锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要花费CAS操做来加锁和解锁。spa
流程图中展现偏向锁的获取释放以及升级至轻量锁操作系统
1.轻量级锁加锁:
线程在执行同步块以前,JVM会先在当前线程的栈桢中建立用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。而后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程便尝试使用自旋来获取锁。线程
2.轻量级锁解锁
轻量级解锁时,会使用原子的CAS操做来将Displaced Mark Word替换回到对象头,若是成功,则表示没有竞争发生。若是失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,致使锁膨胀的流程图。
借用网上流程图以下:
当竟争存在时,若是线程能够很快得到锁,那么能够不在OS层挂起线程(线程切换平均消耗8K个时钟周期),让线程多作几个空操做(自旋)
锁 | 优势 | 缺点 | 适用场景 |
偏向锁 | 加锁和解锁不须要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 | 若是线程间存在锁竞争,会带来额外的锁撤销的消耗。 | 适用于只有一个线程访问同步块场景。</td |
轻量级锁 | 竞争的线程不会阻塞,提升了程序的响应速度。 | 若是始终得不到锁竞争的线程使用自旋会消耗CPU | 追求响应时间。同步块执行速度很是快。 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU。 | 线程阻塞,响应时间缓慢。 | 追求吞吐量。同步块执行速度较长。 |
内置于JVM中的获取锁的优化方法与获取锁的步骤