Java 锁优化

1、重量级锁安全

 
  Java中,Synchronized是经过对象内部的一个叫作监视器锁(monitor)来实现的。可是监视器锁本质又是依赖于底层的操做系统的Mutex Lock来实现的。而操做系统实现线程之间的切换这就须要从用户态转换到核心态,这个成本很是高,状态之间的转换须要相对比较长的时间,这就是为何Synchronized效率低的缘由。所以,这种依赖于操做系统Mutex Lock所实现的锁咱们称之为“重量级锁”。JDK中对Synchronized作的种种优化,其核心都是为了减小这种重量级锁的使用。JDK1.6之后,为了减小得到锁和释放锁所带来的性能消耗,提升性能,引入了“轻量级锁”和“偏向锁”。

2、轻量级锁 多线程

  锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁能够从偏向锁升级到轻量级锁,再升级的重量级锁(可是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,咱们也能够经过-XX:-UseBiasedLocking来禁用偏向锁。锁的状态保存在对象的头文件中,以32位的JDK为例:性能

锁状态测试

25 bit优化

 
4bit

1bitspa

2bit操作系统

23bit线程

2bit3d

是不是偏向锁指针

锁标志位

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向互斥量(重量级锁)的指针

10

GC标记

11

偏向锁

线程ID

Epoch

对象分代年龄

1

01

无锁

对象的hashCode

对象分代年龄

0

01

  “轻量级”是相对于使用操做系统互斥量来实现的传统锁而言的。可是,首先须要强调一点的是,轻量级锁并非用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减小传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程以前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的状况,若是存在同一时间访问同一锁的状况,就会致使轻量级锁膨胀为重量级锁。

一、轻量级锁的加锁过程

  (1)在代码进入同步块的时候,若是同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图2.1所示。

  (2)拷贝对象头中的Mark Word复制到锁记录中。

  (3)拷贝成功后,虚拟机将使用CAS操做尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。若是更新成功,则执行步骤(3),不然执行步骤(4)。

  (4)若是这个更新动做成功了,那么这个线程就拥有了该对象的锁,而且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图2.2所示。

  (5)若是这个更新操做失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,若是是就说明当前线程已经拥有了这个对象的锁,那就能够直接进入同步块继续执行。不然说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了避免让线程阻塞,而采用循环去获取锁的过程。

 

                     图2.1 轻量级锁CAS操做以前堆栈与对象的状态

 

                      图2.2 轻量级锁CAS操做以后堆栈与对象的状态

 
二、轻量级锁的解锁过程:
 
  (1)经过CAS操做尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。
 
  (2)若是替换成功,整个同步过程就完成了。
 
  (3)若是替换失败,说明有其余线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。
 
三、总结:
 
  Java的多线程安全是基于Lock机制实现的,而Lock的性能每每不如人意。缘由是,monitorenter与monitorexit这两个控制多线程同步的bytecode原语,是JVM依赖操做系统互斥(mutex)来实现的。互斥是一种会致使线程挂起,并在较短的时间内又须要从新调度回原线程的,较为消耗资源的操做。
 
  为了优化Java的Lock机制,从Java6开始引入了轻量级锁的概念。轻量级锁(Lightweight Locking)本意是为了减小多线程进入互斥的概率,并非要替代互斥。它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救。

3、偏向锁

  偏向锁会偏向第一个获取它的线程。引入偏向锁是为了在无多线程竞争的状况下尽可能减小没必要要的轻量级锁执行路径,由于轻量级锁的获取及释放依赖屡次CAS原子指令,而偏向锁只须要在置换ThreadID的时候依赖一次CAS原子指令(因为一旦出现多线程竞争的状况就必须撤销偏向锁,因此偏向锁的撤销操做的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提升性能,而偏向锁则是在只有一个线程执行同步块时进一步提升性能。

一、偏向锁获取过程:

  (1)访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。

  (2)若是为可偏向状态,则测试线程ID是否指向当前线程,若是是,进入步骤(5),不然进入步骤(3)。

  (3)若是线程ID并未指向当前线程,则经过CAS操做竞争锁。若是竞争成功,则将Mark Word中线程ID设置为当前线程ID,而后执行(5);若是竞争失败,执行(4)。

  (4)若是CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时得到偏向锁的线程被挂起,偏向锁升级为轻量级锁,而后被阻塞在安全点的线程继续往下执行同步代码。

  (5)执行同步代码。

二、偏向锁的释放:

  偏向锁的撤销在上述第四步骤中有提到偏向锁只有遇到其余线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,须要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

三、重量级锁、轻量级锁和偏向锁之间转换

   

                                        图 2.3三者的转换图

 
  该图主要是对上述内容的总结,若是对上述内容有较好的了解的话,该图应该很容易看懂。 
相关文章
相关标签/搜索