java锁优化策略

前置知识点:对象头

要了解锁优化策略中的轻量级锁与偏向锁的原理和运做过程,须要先了解Hotspot虚拟机的对象头部分的内存布局。java

对象头(摘自《深刻理解java虚拟机》)

对象头信息是与对象自身定义的数据无关的额外存储成本数组

若是对象是数组类型,则虚拟机用3个Word(字宽,在32位虚拟机中,一字宽等于四字节,即32bit)存储对象头。若是对象是非数组类型,则用2Word存储对象头。一个额外的字宽用于存储数组长度。安全

第一个字宽,用来存储对象自身的运行时数据 如:哈希吗(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,简称“Mark Word”。多线程

第二个字宽用于存储指向方法区对象类型数据的指针。布局

对象头信息会根据对象的状态复用本身的存储空间。例如:在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32bit空间中的25bit用于存储对象哈希吗(HashCode),4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0。性能

整个对象头以下图:第三行即为第三部分,非数组类型的没有第三个字宽。优化

clipboard.png

Mark Word的默认存储结构以下图:spa

clipboard.png

在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储如下4种数据: 线程

clipboard.png

在64位虚拟机下,Mark Word是64bit大小的,其存储结构以下: 指针

clipboard.png

锁状态

Java SE1.6为了减小得到锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,

因此在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争状况逐渐升级。

锁能够升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。

这种锁升级却不能降级的策略,目的是为了提升得到锁和释放锁的效率,下文会详细分析。

clipboard.png

偏向锁

Hotspot的做者通过以往的研究发现大多数状况下锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入了偏向锁。

在没有实际竞争的状况下,还可以针对部分场景继续优化。若是不只仅没有实际竞争,自始至终,使用锁的线程都只有一个,那么,维护轻量级锁都是浪费的。偏向锁的目标是,减小无竞争且只有一个线程使用锁的状况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少须要一次CAS,但偏向锁只有初始化时须要一次CAS。

配置

默认-XX:+UseBiasedLocking=true
-XX:-UseBiasedLocking=false关闭偏向锁
应用程序启动几秒钟以后才激活
-XX:BiasedLockingStartupDelay = 0关闭延迟

加锁流程

clipboard.png

获取偏向锁

根据上图,咱们能够看到,首先判断锁对象是否是可偏向对象(若锁对象已经被轻量级锁定或者重量级锁定了,由于锁不会降级,因此它是不可偏向,一样的,关闭了偏向锁的设置-UseBiasedLocking=false,也会形成锁对象不可偏向

  1. 接下来,咱们假定锁对象处于可偏向状态,而且ThreadID为0即biasable & unbiased状态(这里不讨论epoch和age)
  2. 当一个线程试图锁住一个处于biasable & unbiased状态的对象时,经过一个CAS将本身的ThreadID放置到Mark Word中相应的位置,若是CAS操做成功进入第(3)步不然进入(4)步
  3. 当进入到这一步时表明当前没有锁竞争,锁对象继续保持biasable可偏向状态,可是这时ThreadID字段被设置成了偏向锁全部者的ID,而后进入到第(6)步
  4. 当前线程执行CAS获取偏向锁失败(这一步是偏向锁的关键),表示在该锁对象上存在竞争而且这个时候另一个线程在持有偏向锁全部权。这个时候,就要判断持有偏向锁的线程是否还活着,由于一个线程执行完同步代码块后,不会主动释放偏向锁。若是持有偏向锁的线程还活着,将偏向锁消除,膨胀为轻量级锁,不然,将偏向锁消除,让争锁的线程持有偏向锁。
    具体过程是:当到达全局安全点(safepoint,在这个时间点上没有字节码正在执行)时拥有偏向锁的线程被挂起,而后检查持有偏向锁的线程是否活着,若是线程不处于活动状态,则将对象头设置成无锁状态,若是线程仍然活着,锁对象有可能升级为轻量级锁状态(锁标志位置为00),阻塞在安全点的原持有线程被释放,进入到轻量级锁的执行路径中,继续往下执行同步代码。
  5. 当一个线程试图锁住一个处于biasable & biased而且ThreadID不等于本身的ID时,这时因为存在锁竞争必须进入到第(4)步来撤销偏向锁。
  6. 运行同步代码块

clipboard.png

轻量级锁

轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的状况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;

加锁过程

  1. 在代码进入同步块的时候,若是此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁记录目前的Mark Word的拷贝(称为Displaced Mark Word)以及记录锁对象的指针owner。

clipboard.png

  1. 当一个线程来获取这个锁,虚拟机将使用CAS操做尝试将锁对象的Mark Word更新为指向Lock Record的指针。若是这个更新动做成功了,那么这个线程就拥有了该对象的锁,而且对象Mark Word的锁标志位(Mark Word的最后2bit)将转变为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态以下:

clipboard.png

  1. 若是这个更新操做失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧。若是指向,说明当前线程已经拥有了这个对象的锁,那就能够直接进入同步块继续执行。
    不然说明这个锁对象已经被其余线程抢占了。那么它就会自旋等待锁,必定次数后仍未得到锁对象,则修改mark word,将其修改成重量级锁的指针,表示该锁对象进入了重量锁状态。若是有两条以上的线程争用同一个锁,那轻量级锁就再也不有效,要膨胀为重量级锁,锁标志的状态值变为”10”,Mark Word中存储的就是指向重量级(互斥量)的指针。
  2. 由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间的,另外一边,持有轻量级锁的线程,以前在获取锁的时候它拷贝了锁对象头的mark word,在释放锁的时候若是它发如今它持有锁的期间有其余线程来尝试获取锁了,而且该线程对mark word作了修改,二者比对发现不一致致使释放锁的CAS失败,因而也切换到重量锁,释放轻量锁,并唤醒阻塞的线程。

clipboard.png

相关文章
相关标签/搜索